diff --git a/components/lib/dropdown/Dropdown.js b/components/lib/dropdown/Dropdown.js index 9be0ed8be8..75e87941eb 100644 --- a/components/lib/dropdown/Dropdown.js +++ b/components/lib/dropdown/Dropdown.js @@ -78,7 +78,7 @@ export const Dropdown = React.memo( }; const isClearClicked = (event) => { - return DomHandler.hasClass(event.target, 'p-dropdown-clear-icon') || DomHandler.hasClass(event.target, 'p-dropdown-filter-clear-icon'); + return DomHandler.isAttributeEquals(event.target, 'data-pc-section', 'clearicon') || DomHandler.isAttributeEquals(event.target.parentElement || event.target, 'data-pc-section', 'filterclearicon'); }; const onClick = (event) => { @@ -93,7 +93,7 @@ export const Dropdown = React.memo( return; } - if (DomHandler.hasClass(event.target, 'p-dropdown-clear-icon') || event.target.tagName === 'INPUT') { + if (isClearClicked(event) || event.target.tagName === 'INPUT') { return; } else if (!overlayRef.current || !(overlayRef.current && overlayRef.current.contains(event.target))) { DomHandler.focus(focusInputRef.current); @@ -561,7 +561,7 @@ export const Dropdown = React.memo( }; const scrollInView = () => { - const highlightItem = DomHandler.findSingle(overlayRef.current, 'li.p-highlight'); + const highlightItem = DomHandler.findSingle(overlayRef.current, 'li[data-p-highlight="true"]'); if (highlightItem && highlightItem.scrollIntoView) { highlightItem.scrollIntoView({ block: 'nearest', inline: 'nearest' }); diff --git a/components/lib/dropdown/DropdownBase.js b/components/lib/dropdown/DropdownBase.js index 392a9f80fc..52a8243731 100644 --- a/components/lib/dropdown/DropdownBase.js +++ b/components/lib/dropdown/DropdownBase.js @@ -28,6 +28,7 @@ const classes = { loadingIcon: 'p-dropdown-trigger-icon p-clickable', clearIcon: 'p-dropdown-clear-icon p-clickable', filterIcon: 'p-dropdown-filter-icon', + filterClearIcon: 'p-dropdown-filter-clear-icon', filterContainer: ({ clearIcon }) => classNames('p-dropdown-filter-container', { 'p-dropdown-clearable-filter': !!clearIcon }), filterInput: 'p-dropdown-filter p-inputtext p-component', list: ({ virtualScrollerOptions }) => (virtualScrollerOptions ? 'p-dropdown-items' : 'p-dropdown-items'), diff --git a/components/lib/dropdown/DropdownPanel.js b/components/lib/dropdown/DropdownPanel.js index 0009205c5f..7e0f9da89a 100644 --- a/components/lib/dropdown/DropdownPanel.js +++ b/components/lib/dropdown/DropdownPanel.js @@ -105,7 +105,8 @@ export const DropdownPanel = React.memo( const itemGroupProps = mergeProps( { className: cx('itemGroup', { optionGroupLabel }), - style + style, + 'data-p-highlight': props.selected }, getPTOptions('itemGroup') ); @@ -140,11 +141,11 @@ export const DropdownPanel = React.memo( const ariaLabel = localeOption('clear'); const clearIconProps = mergeProps( { - className: cx('clearIcon'), + className: cx('filterClearIcon'), 'aria-label': ariaLabel, onClick: () => props.onFilterClearIconClick(() => DomHandler.focus(filterInputRef.current)) }, - getPTOptions('clearIcon') + getPTOptions('filterClearIcon') ); const icon = props.filterClearIcon || ; const filterClearIcon = IconUtils.getJSXIcon(icon, { ...clearIconProps }, { props }); diff --git a/components/lib/listbox/ListBoxItem.js b/components/lib/listbox/ListBoxItem.js index e5a5b34eff..b5fdabb0cc 100644 --- a/components/lib/listbox/ListBoxItem.js +++ b/components/lib/listbox/ListBoxItem.js @@ -83,13 +83,13 @@ export const ListBoxItem = React.memo((props) => { const findNextItem = (item) => { const nextItem = item.nextElementSibling; - return nextItem ? (DomHandler.hasClass(nextItem, 'p-disabled') || DomHandler.hasClass(nextItem, 'p-listbox-item-group') ? findNextItem(nextItem) : nextItem) : null; + return nextItem ? (DomHandler.isAttributeEquals(nextItem, 'data-p-disabled', true) || DomHandler.isAttributeEquals(nextItem, 'data-pc-section', 'itemgroup') ? findNextItem(nextItem) : nextItem) : null; }; const findPrevItem = (item) => { const prevItem = item.previousElementSibling; - return prevItem ? (DomHandler.hasClass(prevItem, 'p-disabled') || DomHandler.hasClass(prevItem, 'p-listbox-item-group') ? findPrevItem(prevItem) : prevItem) : null; + return prevItem ? (DomHandler.isAttributeEquals(prevItem, 'data-p-disabled', true) || DomHandler.isAttributeEquals(prevItem, 'data-pc-section', 'itemgroup') ? findPrevItem(prevItem) : prevItem) : null; }; const content = props.template ? ObjectUtils.getJSXElement(props.template, props.option) : props.label; @@ -108,7 +108,8 @@ export const ListBoxItem = React.memo((props) => { key: props.label, role: 'option', 'aria-selected': props.selected, - 'aria-disabled': props.disabled + 'aria-disabled': props.disabled, + 'data-p-disabled': props.disabled }, getPTOptions('item') ); diff --git a/components/lib/mention/Mention.js b/components/lib/mention/Mention.js index c201b38633..460634ca39 100644 --- a/components/lib/mention/Mention.js +++ b/components/lib/mention/Mention.js @@ -19,6 +19,8 @@ export const Mention = React.memo( const [focusedState, setFocusedState] = React.useState(false); const [searchingState, setSearchingState] = React.useState(false); const [triggerState, setTriggerState] = React.useState(null); + const [highlightState, setHighlightState] = React.useState([]); + const elementRef = React.useRef(null); const overlayRef = React.useRef(null); const inputRef = React.useRef(props.inputRef); @@ -37,10 +39,13 @@ export const Mention = React.memo( useHandleStyle(MentionBase.css.styles, isUnstyled, { name: 'mention' }); - const getPTOptions = (item, suggestion) => { + const getPTOptions = (item, suggestion, options) => { return ptm(suggestion, { context: { trigger: triggerState ? triggerState.key : '' + }, + state: { + ...options } }); }; @@ -72,7 +77,13 @@ export const Mention = React.memo( const onOverlayEntering = () => { if (props.autoHighlight && props.suggestions && props.suggestions.length) { - DomHandler.addClass(listRef.current.firstChild, 'p-highlight'); + setHighlightState((prevState) => { + const newState = [...prevState]; + + newState[0] = true; + + return newState; + }); } }; @@ -92,13 +103,15 @@ export const Mention = React.memo( }; const alignOverlay = () => { - const { key, index } = triggerState; - const value = inputRef.current.value; - const position = DomHandler.getCursorOffset(inputRef.current, value.substring(0, index - 1), value.substring(index), key); - - overlayRef.current.style.transformOrigin = 'top'; - overlayRef.current.style.left = `calc(${position.left}px + 1rem)`; - overlayRef.current.style.top = `calc(${position.top}px + 1.2rem)`; + if (triggerState) { + const { key, index } = triggerState; + const value = inputRef.current.value; + const position = DomHandler.getCursorOffset(inputRef.current, value.substring(0, index - 1), value.substring(index), key); + + overlayRef.current.style.transformOrigin = 'top'; + overlayRef.current.style.left = `calc(${position.left}px + 1rem)`; + overlayRef.current.style.top = `calc(${position.top}px + 1.2rem)`; + } }; const onPanelClick = (event) => { @@ -245,9 +258,19 @@ export const Mention = React.memo( const onInput = (event) => { props.onInput && props.onInput(event); + const isFilled = event.target.value.length > 0; - if (event.target.value.length > 0) DomHandler.addClass(elementRef.current, 'p-inputwrapper-filled'); - else DomHandler.removeClass(elementRef.current, 'p-inputwrapper-filled'); + if (isUnstyled()) { + DomHandler.setAttributes(elementRef.current, { + 'data-p-inputwrapper-filled': isFilled + }); + } else { + if (isFilled) { + DomHandler.addClass(elementRef.current, 'p-inputwrapper-filled'); + } else { + DomHandler.removeClass(elementRef.current, 'p-inputwrapper-filled'); + } + } }; const onKeyUp = (event) => { @@ -264,7 +287,7 @@ export const Mention = React.memo( const onKeyDown = (event) => { if (overlayVisibleState) { - let highlightItem = DomHandler.findSingle(overlayRef.current, 'li.p-highlight'); + let highlightItem = DomHandler.findSingle(overlayRef.current, 'li[data-p-highlight="true"]'); switch (event.which) { //down @@ -273,15 +296,33 @@ export const Mention = React.memo( let nextElement = highlightItem.nextElementSibling; if (nextElement) { - DomHandler.addClass(nextElement, 'p-highlight'); - DomHandler.removeClass(highlightItem, 'p-highlight'); + const nextElementIndex = DomHandler.index(nextElement); + const highlightItemIndex = DomHandler.index(highlightItem); + + setHighlightState((prevState) => { + const newState = [...prevState]; + + newState[nextElementIndex] = true; + newState[highlightItemIndex] = false; + + return newState; + }); + DomHandler.scrollInView(overlayRef.current, nextElement); } } else { highlightItem = DomHandler.findSingle(overlayRef.current, 'li'); if (highlightItem) { - DomHandler.addClass(highlightItem, 'p-highlight'); + const highlightItemIndex = DomHandler.index(highlightItem); + + setHighlightState((prevState) => { + const newState = [...prevState]; + + newState[highlightItemIndex] = true; + + return newState; + }); } } @@ -294,8 +335,18 @@ export const Mention = React.memo( let previousElement = highlightItem.previousElementSibling; if (previousElement) { - DomHandler.addClass(previousElement, 'p-highlight'); - DomHandler.removeClass(highlightItem, 'p-highlight'); + const previousElementIndex = DomHandler.index(previousElement); + const highlightItemIndex = DomHandler.index(highlightItem); + + setHighlightState((prevState) => { + const newState = [...prevState]; + + newState[previousElementIndex] = true; + newState[highlightItemIndex] = false; + + return newState; + }); + DomHandler.scrollInView(overlayRef.current, previousElement); } } @@ -353,16 +404,31 @@ export const Mention = React.memo( }, [inputRef, props.inputRef]); useUpdateEffect(() => { + const hasSuggestions = props.suggestions && props.suggestions.length; + + if (hasSuggestions) { + const newState = props.suggestions.map(() => false); + + setHighlightState(newState); + } + if (searchingState) { - props.suggestions && props.suggestions.length ? show() : hide(); + hasSuggestions ? show() : hide(); overlayVisibleState && alignOverlay(); setSearchingState(false); } }, [props.suggestions]); useUpdateEffect(() => { - if (!isFilled && DomHandler.hasClass(elementRef.current, 'p-inputwrapper-filled')) { - DomHandler.removeClass(elementRef.current, 'p-inputwrapper-filled'); + const _isUnstyled = isUnstyled(); + const isInputWrapperFilled = _isUnstyled ? DomHandler.isAttributeEquals(elementRef.current, 'data-p-inputwrapper-filled', true) : DomHandler.hasClass(elementRef.current, 'p-inputwrapper-filled'); + + if (!isFilled && isInputWrapperFilled) { + _isUnstyled + ? DomHandler.setAttributes(elementRef.current, { + 'data-p-inputwrapper-filled': false + }) + : DomHandler.removeClass(elementRef.current, 'p-inputwrapper-filled'); } }, [isFilled]); @@ -373,14 +439,16 @@ export const Mention = React.memo( const createItem = (suggestion, index) => { const key = index + '_item'; const content = props.itemTemplate ? ObjectUtils.getJSXElement(props.itemTemplate, suggestion, { trigger: triggerState ? triggerState.key : '', index }) : formatValue(suggestion); + const isSelected = highlightState[index]; const itemProps = mergeProps( { key: key, - className: cx('item'), - onClick: (e) => onItemClick(e, suggestion) + className: cx('item', { isSelected }), + onClick: (e) => onItemClick(e, suggestion), + 'data-p-highlight': isSelected }, - getPTOptions(suggestion, 'item') + getPTOptions(suggestion, 'item', { selected: isSelected }) ); return ( diff --git a/components/lib/mention/MentionBase.js b/components/lib/mention/MentionBase.js index 6964ba718c..d8ff46ae7d 100644 --- a/components/lib/mention/MentionBase.js +++ b/components/lib/mention/MentionBase.js @@ -2,7 +2,10 @@ import { ComponentBase } from '../componentbase/ComponentBase'; import { classNames } from '../utils/Utils'; const classes = { - item: 'p-mention-item', + item: ({ isSelected }) => + classNames('p-mention-item', { + 'p-highlight': isSelected + }), items: 'p-mention-items', panel: ({ props }) => classNames('p-mention-panel p-component', props.panelClassName), input: ({ props }) => classNames('p-mention-input', props.inputClassName), diff --git a/components/lib/mention/mention.d.ts b/components/lib/mention/mention.d.ts index d5bce95152..9e9a7d311a 100644 --- a/components/lib/mention/mention.d.ts +++ b/components/lib/mention/mention.d.ts @@ -86,6 +86,10 @@ export interface MentionState { * Current trigger state. */ trigger: any; + /** + * For item, this is the state of the item. + */ + selected?: boolean; } /** diff --git a/components/lib/multiselect/MultiSelect.js b/components/lib/multiselect/MultiSelect.js index 4441803d85..491557ffd0 100644 --- a/components/lib/multiselect/MultiSelect.js +++ b/components/lib/multiselect/MultiSelect.js @@ -267,7 +267,7 @@ export const MultiSelect = React.memo( }; const scrollInView = () => { - const highlightItem = DomHandler.findSingle(overlayRef.current, 'li.p-highlight'); + const highlightItem = DomHandler.findSingle(overlayRef.current, 'li[data-p-highlight="true"]'); if (highlightItem && highlightItem.scrollIntoView) { highlightItem.scrollIntoView({ block: 'nearest', inline: 'nearest' }); diff --git a/components/lib/passthrough/tailwind/index.js b/components/lib/passthrough/tailwind/index.js index 8676c40446..2673181640 100644 --- a/components/lib/passthrough/tailwind/index.js +++ b/components/lib/passthrough/tailwind/index.js @@ -1286,7 +1286,11 @@ const Tailwind = { root: 'relative', panel: 'max-h-[200px] min-w-full overflow-auto bg-white dark:bg-gray-900 text-gray-700 dark:text-white/80 border-0 rounded-md shadow-lg', items: 'py-3 list-none m-0', - item: 'cursor-pointer font-normal overflow-hidden relative whitespace-nowrap m-0 p-3 border-0 transition-shadow duration-200 rounded-none dark:text-white/80 dark:hover:bg-gray-800 hover:text-gray-700 hover:bg-gray-200', + item: ({ state }) => ({ + className: classNames('cursor-pointer font-normal overflow-hidden relative whitespace-nowrap m-0 p-3 border-0 transition-shadow duration-200 rounded-none dark:text-white/80 dark:hover:bg-gray-800 hover:text-gray-700 hover:bg-gray-200', { + 'bg-blue-50 text-blue-700 dark:bg-blue-300 dark:text-white/80': state.selected + }) + }), transition: TRANSITIONS.overlay }, multiselect: { diff --git a/components/lib/tree/UITreeNode.js b/components/lib/tree/UITreeNode.js index 7b0340e9f4..12378ade11 100644 --- a/components/lib/tree/UITreeNode.js +++ b/components/lib/tree/UITreeNode.js @@ -803,7 +803,8 @@ export const UITreeNode = React.memo((props) => { onDragEnter: onDragEnter, onDragLeave: onDragLeave, onDragStart: onDragStart, - onDragEnd: onDragEnd + onDragEnd: onDragEnd, + 'data-p-highlight': isCheckboxSelectionMode() ? checked : selected }, getPTOptions('content') ); diff --git a/components/lib/treeselect/TreeSelect.js b/components/lib/treeselect/TreeSelect.js index 880cf0436a..0a3bec8790 100644 --- a/components/lib/treeselect/TreeSelect.js +++ b/components/lib/treeselect/TreeSelect.js @@ -95,7 +95,7 @@ export const TreeSelect = React.memo( }; const onClick = (event) => { - if (!props.disabled && (!overlayRef.current || !overlayRef.current.contains(event.target)) && !DomHandler.hasClass(event.target, 'p-treeselect-close')) { + if (!props.disabled && (!overlayRef.current || !overlayRef.current.contains(event.target)) && !DomHandler.isAttributeEquals(event.target, 'data-pc-section', 'closebutton')) { DomHandler.focus(focusInputRef.current); overlayVisibleState ? hide() : show(); } @@ -341,7 +341,7 @@ export const TreeSelect = React.memo( }; const scrollInView = () => { - const highlightItem = DomHandler.findSingle(overlayRef.current, '.p-treenode-content.p-highlight'); + const highlightItem = DomHandler.findSingle(overlayRef.current, '[data-pc-section="content"][data-p-highlight="true"]'); if (highlightItem && highlightItem.scrollIntoView) { highlightItem.scrollIntoView({ block: 'nearest', inline: 'start' });