diff --git a/e2e/components/ComboBox/ComboBox-test.avt.e2e.js b/e2e/components/ComboBox/ComboBox-test.avt.e2e.js index ae3fba30cc19..d38f31388d28 100644 --- a/e2e/components/ComboBox/ComboBox-test.avt.e2e.js +++ b/e2e/components/ComboBox/ComboBox-test.avt.e2e.js @@ -86,7 +86,6 @@ test.describe('@avt ComboBox', () => { await expect(menu).toBeVisible(); // Navigation inside the menu // move to first option - await page.keyboard.press('ArrowDown'); await expect(optionOne).toHaveClass( 'cds--list-box__menu-item cds--list-box__menu-item--highlighted' ); @@ -114,5 +113,24 @@ test.describe('@avt ComboBox', () => { // Should select and populate combobox with current filtered item await page.keyboard.press('Enter'); await expect(combobox).toHaveValue('Option 2'); + // clear to prep for general selection + await page.keyboard.press('Escape'); + await expect(clearButton).toBeHidden(); + await expect(combobox).toHaveValue(''); + + // should open and select option 1 + await page.keyboard.press('Enter'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + await expect(combobox).toHaveValue('Option 1'); + await page.keyboard.press('Escape'); + + // should open and select option 2 + await page.keyboard.press('Enter'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + await expect(combobox).toHaveValue('Option 2'); + await page.keyboard.press('Escape'); }); }); diff --git a/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js b/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js index 8612988a4af6..11290bc149eb 100644 --- a/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js +++ b/e2e/components/FluidComboBox/FluidComboBox-test.avt.e2e.js @@ -86,7 +86,6 @@ test.describe('@avt FluidComboBox', () => { await expect(menu).toBeVisible(); // Navigation inside the menu // move to first option - await page.keyboard.press('ArrowDown'); await expect(optionOne).toHaveClass( 'cds--list-box__menu-item cds--list-box__menu-item--highlighted' ); diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index d4097cf11463..fdf8f8de43ef 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -1116,141 +1116,7 @@ Map { "type": "bool", }, "downshiftProps": Object { - "args": Array [ - Object { - "children": Object { - "type": "func", - }, - "defaultHighlightedIndex": Object { - "type": "number", - }, - "defaultIsOpen": Object { - "type": "bool", - }, - "environment": Object { - "args": Array [ - Object { - "Node": Object { - "isRequired": true, - "type": "func", - }, - "addEventListener": Object { - "isRequired": true, - "type": "func", - }, - "document": Object { - "args": Array [ - Object { - "activeElement": Object { - "isRequired": true, - "type": "any", - }, - "body": Object { - "isRequired": true, - "type": "any", - }, - "createElement": Object { - "isRequired": true, - "type": "func", - }, - "getElementById": Object { - "isRequired": true, - "type": "func", - }, - }, - ], - "isRequired": true, - "type": "shape", - }, - "removeEventListener": Object { - "isRequired": true, - "type": "func", - }, - }, - ], - "type": "shape", - }, - "getA11yStatusMessage": Object { - "type": "func", - }, - "getItemId": Object { - "type": "func", - }, - "highlightedIndex": Object { - "type": "number", - }, - "id": Object { - "type": "string", - }, - "initialHighlightedIndex": Object { - "type": "number", - }, - "initialInputValue": Object { - "type": "string", - }, - "initialIsOpen": Object { - "type": "bool", - }, - "initialSelectedItem": Object { - "type": "any", - }, - "inputId": Object { - "type": "string", - }, - "inputValue": Object { - "type": "string", - }, - "isOpen": Object { - "type": "bool", - }, - "itemCount": Object { - "type": "number", - }, - "itemToString": Object { - "type": "func", - }, - "labelId": Object { - "type": "string", - }, - "menuId": Object { - "type": "string", - }, - "onChange": Object { - "type": "func", - }, - "onInputValueChange": Object { - "type": "func", - }, - "onOuterClick": Object { - "type": "func", - }, - "onSelect": Object { - "type": "func", - }, - "onStateChange": Object { - "type": "func", - }, - "onUserAction": Object { - "type": "func", - }, - "scrollIntoView": Object { - "type": "func", - }, - "selectedItem": Object { - "type": "any", - }, - "selectedItemChanged": Object { - "type": "func", - }, - "stateReducer": Object { - "type": "func", - }, - "suppressRefError": Object { - "type": "bool", - }, - }, - ], - "type": "shape", + "type": "object", }, "helperText": Object { "type": "node", diff --git a/packages/react/src/components/ComboBox/ComboBox-test.js b/packages/react/src/components/ComboBox/ComboBox-test.js index 7aa22d7d3203..e98ff05d2c09 100644 --- a/packages/react/src/components/ComboBox/ComboBox-test.js +++ b/packages/react/src/components/ComboBox/ComboBox-test.js @@ -21,7 +21,7 @@ import { Slug } from '../Slug'; const findInputNode = () => screen.getByRole('combobox'); const openMenu = async () => { - await userEvent.click(findInputNode()); + await userEvent.click(screen.getByTitle('Open')); }; const prefix = 'cds'; @@ -105,6 +105,29 @@ describe('ComboBox', () => { }); }); + it('should not let the user select an option by clicking on the disabled option node', async () => { + mockProps.items[2].disabled = true; + + render(); + await openMenu(); + + await userEvent.click(screen.getAllByRole('option')[2]); + + expect(mockProps.onChange).not.toHaveBeenCalled(); + }); + + it('should not select the disabled option if user type in input and press enter', async () => { + mockProps.items[2].disabled = true; + + render(); + await userEvent.type(findInputNode(), 'Item 2'); + await userEvent.keyboard('[Enter]'); + + expect(mockProps.onChange).not.toHaveBeenCalled(); + //it should not close the menu if matching element not found and enter is pressed. + expect(findListBoxNode()).toHaveClass(`${prefix}--list-box--expanded`); + }); + it('should retain value if custom value is entered and `allowCustomValue` is set', async () => { render(); diff --git a/packages/react/src/components/ComboBox/ComboBox.stories.js b/packages/react/src/components/ComboBox/ComboBox.stories.js index b987255bf215..dde72b5b4618 100644 --- a/packages/react/src/components/ComboBox/ComboBox.stories.js +++ b/packages/react/src/components/ComboBox/ComboBox.stories.js @@ -67,11 +67,6 @@ export const Default = () => ( onChange={() => {}} id="carbon-combobox" items={items} - downshiftProps={{ - onStateChange: () => { - console.log('the state has changed'); - }, - }} itemToString={(item) => (item ? item.text : '')} titleText="ComboBox title" helperText="Combobox helper text" @@ -88,16 +83,9 @@ export const AllowCustomValue = () => { { - console.log(e); - }} + onChange={() => {}} id="carbon-combobox" items={['Apple', 'Orange', 'Banana', 'Pineapple', 'Raspberry', 'Lime']} - downshiftProps={{ - onStateChange: () => { - console.log('the state has changed'); - }, - }} titleText="ComboBox title" helperText="Combobox helper text" /> @@ -127,11 +115,6 @@ export const Playground = (args) => ( { - console.log('the state has changed'); - }, - }} itemToString={(item) => (item ? item.text : '')} titleText="ComboBox title" helperText="Combobox helper text" @@ -192,7 +175,7 @@ Playground.argTypes = { onChange: { action: 'changed', }, - onClick: { + onToggleClick: { action: 'clicked', }, onInputChange: { @@ -200,11 +183,6 @@ Playground.argTypes = { disable: true, }, }, - onToggleClick: { - table: { - disable: true, - }, - }, selectedItem: { table: { disable: true, diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 812be8b8062f..5e2c27f66757 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -6,10 +6,7 @@ */ import cx from 'classnames'; -import Downshift, { - ControllerStateAndHelpers, - StateChangeOptions, -} from 'downshift'; +import { useCombobox, UseComboboxProps } from 'downshift'; import PropTypes, { ReactNodeLike } from 'prop-types'; import React, { useContext, @@ -17,7 +14,6 @@ import React, { useState, useRef, forwardRef, - type ComponentProps, type ReactNode, type ComponentType, type ForwardedRef, @@ -49,16 +45,15 @@ import { usePrefix } from '../../internal/usePrefix'; import { FormContext } from '../FluidForm'; const { - keyDownArrowDown, - keyDownArrowUp, - keyDownEscape, - clickButton, - clickItem, - blurButton, - changeInput, - blurInput, - unknown, -} = Downshift.stateChangeTypes; + InputBlur, + InputKeyDownEnter, + FunctionToggleMenu, + ToggleButtonClick, + ItemMouseMove, + InputKeyDownArrowUp, + InputKeyDownArrowDown, + MenuMouseLeave, +} = useCombobox.stateChangeTypes; const defaultItemToString = (item: ItemType | null) => { if (typeof item === 'string') { @@ -117,7 +112,7 @@ const findHighlightedIndex = ( for (let i = 0; i < items.length; i++) { const item = itemToString(items[i]).toLowerCase(); - if (item.indexOf(searchValue) !== -1) { + if (!items[i]['disabled'] && item.indexOf(searchValue) !== -1) { return i; } } @@ -135,7 +130,6 @@ interface OnChangeData { } type ItemToStringHandler = (item: ItemType | null) => string; - export interface ComboBoxProps extends Omit, ExcludedAttributes> { /** @@ -174,7 +168,7 @@ export interface ComboBoxProps /** * Additional props passed to Downshift */ - downshiftProps?: ComponentProps>; + downshiftProps?: Partial>; /** * Provide helper text that is used alongside the control label for @@ -337,7 +331,7 @@ const ComboBox = forwardRef( onToggleClick, placeholder, readOnly, - selectedItem, + selectedItem: selectedItemProp, shouldFilterItem = defaultShouldFilterItem, size, titleText, @@ -357,25 +351,24 @@ const ComboBox = forwardRef( initialSelectedItem, inputValue: '', itemToString, - selectedItem, + selectedItem: selectedItemProp, }) ); const [isFocused, setIsFocused] = useState(false); const [prevSelectedItem, setPrevSelectedItem] = useState(); const [doneInitialSelectedItem, setDoneInitialSelectedItem] = useState(false); - const [highlightedIndex, setHighlightedIndex] = useState(); const savedOnInputChange = useRef(onInputChange); - if (!doneInitialSelectedItem || prevSelectedItem !== selectedItem) { + if (!doneInitialSelectedItem || prevSelectedItem !== selectedItemProp) { setDoneInitialSelectedItem(true); - setPrevSelectedItem(selectedItem); + setPrevSelectedItem(selectedItemProp); setInputValue( getInputValue({ initialSelectedItem, inputValue, itemToString, - selectedItem, + selectedItem: selectedItemProp, }) ); } @@ -395,16 +388,6 @@ const ComboBox = forwardRef( : defaultShouldFilterItem() ); - const handleOnChange = (selectedItem: ItemType | null) => { - if (onChange) { - onChange({ selectedItem }); - } - }; - - const handleOnInputValueChange = (inputValue?: string) => { - setInputValue(inputValue || ''); - }; - useEffect(() => { savedOnInputChange.current = onInputChange; }, [onInputChange]); @@ -421,65 +404,75 @@ const ComboBox = forwardRef( } }; - const getHighlightedIndex = (changes: StateChangeOptions) => { - if (Object.prototype.hasOwnProperty.call(changes, 'inputValue')) { - const { inputValue } = changes; - const filteredItems = filterItems( - items, - itemToString, - inputValue || null - ); - const indexToHighlight = findHighlightedIndex( - { - ...props, - items: filteredItems, - }, - inputValue - ); - setHighlightedIndex(indexToHighlight); - return indexToHighlight; - } - return highlightedIndex || 0; - }; + const filteredItems = (inputValue) => + filterItems(items, itemToString, inputValue || null); - const handleOnStateChange = ( - changes: StateChangeOptions, - { - setHighlightedIndex: updateHighlightedIndex, - }: ControllerStateAndHelpers - ) => { - const { type } = changes; - switch (type) { - case keyDownArrowDown: - case keyDownArrowUp: - if (changes.isOpen) { - updateHighlightedIndex(getHighlightedIndex(changes)); - } else { - setHighlightedIndex(changes.highlightedIndex); - } - break; - case blurButton: - case keyDownEscape: - setHighlightedIndex(changes.highlightedIndex); - break; - case changeInput: - updateHighlightedIndex(getHighlightedIndex(changes)); - break; - case blurInput: - if (allowCustomValue) { - setInputValue(inputValue); - if (onChange) { - onChange({ selectedItem, inputValue }); + const indexToHighlight = (inputValue) => + findHighlightedIndex( + { + ...props, + items: filteredItems(inputValue), + }, + inputValue + ); + + const stateReducer = React.useCallback( + (state, actionAndChanges) => { + const { type, changes } = actionAndChanges; + const { highlightedIndex } = changes; + switch (type) { + case InputBlur: + if ( + state.inputValue && + highlightedIndex == '-1' && + !allowCustomValue + ) { + return { ...changes, inputValue: '' }; } - } - break; - case clickButton: - case clickItem: - case unknown: - setHighlightedIndex(getHighlightedIndex(changes)); - break; - } - }; + return changes; + case InputKeyDownEnter: + if (allowCustomValue) { + setInputValue(inputValue); + setHighlightedIndex(changes.selectedItem); + if (onChange) { + onChange({ selectedItem: changes.selectedItem }); + } + return changes; + } else if (changes.selectedItem && !allowCustomValue) { + return changes; + } else { + return { ...changes, isOpen: true }; + } + case FunctionToggleMenu: + case ToggleButtonClick: + if (changes.isOpen && !changes.selectedItem) { + return { ...changes, highlightedIndex: 0 }; + } + return changes; + + case MenuMouseLeave: + return { ...changes, highlightedIndex: state.highlightedIndex }; + + case InputKeyDownArrowUp: + case InputKeyDownArrowDown: + if (highlightedIndex === -1) { + return { + ...changes, + highlightedIndex: 0, + }; + } + return changes; + + case ItemMouseMove: + return { ...changes, highlightedIndex: state.highlightedIndex }; + + default: + return changes; + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [allowCustomValue, inputValue, onChange] + ); const handleToggleClick = (isOpen: boolean) => @@ -539,277 +532,268 @@ const ComboBox = forwardRef( }); } - return ( - { - handleOnStateChange(...args); - downshiftProps?.onStateChange?.(...args); - }} - inputValue={inputValue || ''} - itemToString={itemToString} - initialSelectedItem={initialSelectedItem} - inputId={id} - selectedItem={selectedItem}> - {({ - getInputProps, - getItemProps, - getLabelProps, - getMenuProps, - getRootProps, - getToggleButtonProps, - isOpen, - inputValue, - selectedItem, - clearSelection, - toggleMenu, - }) => { - const rootProps = getRootProps( - // @ts-ignore this is not supposed to be a required property - {}, - { - suppressRefError: true, + const { + getInputProps, + getItemProps, + getLabelProps, + getMenuProps, + getToggleButtonProps, + isOpen, + highlightedIndex, + selectItem, + selectedItem, + toggleMenu, + setHighlightedIndex, + } = useCombobox({ + ...downshiftProps, + items, + inputValue: inputValue, + itemToString: (item) => { + return itemToString(item); + }, + onInputValueChange({ inputValue }) { + setInputValue(inputValue || ''); + setHighlightedIndex(indexToHighlight(inputValue)); + }, + + onSelectedItemChange({ selectedItem }) { + onChange({ selectedItem }); + }, + + initialSelectedItem: initialSelectedItem, + inputId: id, + stateReducer, + isItemDisabled(item, _index) { + return (item as any).disabled; + }, + }); + + const buttonProps = getToggleButtonProps({ + disabled: disabled || readOnly, + onClick: handleToggleClick(isOpen), + // When we moved the "root node" of Downshift to the for + // ARIA 1.2 compliance, we unfortunately hit this branch for the + // "mouseup" event that downshift listens to: + // https://github.com/downshift-js/downshift/blob/v5.2.1/src/downshift.js#L1051-L1065 + // + // As a result, it will reset the state of the component and so we + // stop the event from propagating to prevent this if the menu is already open. + // This allows the toggleMenu behavior for the toggleButton to correctly open and + // close the menu. + onMouseUp(event) { + if (isOpen) { + event.stopPropagation(); + } + }, + }); + + const handleFocus = (evt: FocusEvent) => { + setIsFocused(evt.type === 'focus'); + }; + + const readOnlyEventHandlers = readOnly + ? { + onKeyDown: (evt: KeyboardEvent) => { + // This prevents the select from opening for the above keys + if (evt.key !== 'Tab') { + evt.preventDefault(); } - ); - const labelProps = getLabelProps(); - const buttonProps = getToggleButtonProps({ - disabled: disabled || readOnly, - onClick: handleToggleClick(isOpen), - // When we moved the "root node" of Downshift to the for - // ARIA 1.2 compliance, we unfortunately hit this branch for the - // "mouseup" event that downshift listens to: - // https://github.com/downshift-js/downshift/blob/v5.2.1/src/downshift.js#L1051-L1065 - // - // As a result, it will reset the state of the component and so we - // stop the event from propagating to prevent this if the menu is already open. - // This allows the toggleMenu behavior for the toggleButton to correctly open and - // close the menu. - onMouseUp(event) { - if (isOpen) { - event.stopPropagation(); - } - }, - }); - const inputProps: any = getInputProps({ - disabled, - placeholder, - onClick(): void { - toggleMenu(); - }, - onKeyDown: ( - event: KeyboardEvent & { - preventDownshiftDefault: boolean; - target: { - value: string; - setSelectionRange: (start: number, end: number) => void; - }; - } - ): void => { - if (match(event, keys.Space)) { - event.stopPropagation(); - } + }, + } + : {}; - if ( - match(event, keys.Enter) && - (!inputValue || allowCustomValue) - ) { - toggleMenu(); - - // Since `onChange` does not normally fire when the menu is closed, we should - // manually fire it when `allowCustomValue` is provided, the menu is closing, - // and there is a value. - if (allowCustomValue && isOpen && inputValue) { - onChange({ selectedItem, inputValue }); - } - } + // The input should be described by the appropriate message text id + // when both the message is supplied *and* when the component is in + // the matching state (invalid, warn, etc). + const ariaDescribedBy = + (invalid && invalidText && invalidTextId) || + (warn && warnText && warnTextId) || + (helperText && !isFluid && helperTextId) || + undefined; - if (match(event, keys.Escape) && inputValue) { - if (event.target === textInput.current && isOpen) { - toggleMenu(); - event.preventDownshiftDefault = true; - event?.persist?.(); - } - } + return ( +
+ {titleText && ( + + {titleText} + + )} + +
+ & { + preventDownshiftDefault: boolean; + target: { + value: string; + setSelectionRange: (start: number, end: number) => void; + }; + } + ): void => { + if (match(event, keys.Space)) { + event.stopPropagation(); + } + if ( + match(event, keys.Enter) && + (!inputValue || allowCustomValue) + ) { + toggleMenu(); + + if (highlightedIndex !== -1) { + selectItem(items[highlightedIndex]); + } + + event.preventDownshiftDefault = true; + event?.persist?.(); + } - if (match(event, keys.Home) && event.code !== 'Numpad7') { - event.target.setSelectionRange(0, 0); - } + if (match(event, keys.Escape) && inputValue) { + if (event.target === textInput.current && isOpen) { + toggleMenu(); + event.preventDownshiftDefault = true; + event?.persist?.(); + } + } - if (match(event, keys.End) && event.code !== 'Numpad1') { - event.target.setSelectionRange( - event.target.value.length, - event.target.value.length - ); - } + if (match(event, keys.Home) && event.code !== 'Numpad7') { + event.target.setSelectionRange(0, 0); + } - if (event.altKey && event.key == 'ArrowDown') { - event.preventDownshiftDefault = true; - if (!isOpen) { - toggleMenu(); - } - } - if (event.altKey && event.key == 'ArrowUp') { - event.preventDownshiftDefault = true; - if (isOpen) { - toggleMenu(); - } - } - }, - }); - - const handleFocus = (evt: FocusEvent) => { - setIsFocused(evt.type === 'focus'); - }; - - const readOnlyEventHandlers = readOnly - ? { - onKeyDown: (evt: KeyboardEvent) => { - // This prevents the select from opening for the above keys - if (evt.key !== 'Tab') { - evt.preventDefault(); + if (match(event, keys.End) && event.code !== 'Numpad1') { + event.target.setSelectionRange( + event.target.value.length, + event.target.value.length + ); + } + + if (event.altKey && event.key == 'ArrowDown') { + event.preventDownshiftDefault = true; + if (!isOpen) { + toggleMenu(); + } + } + if (event.altKey && event.key == 'ArrowUp') { + event.preventDownshiftDefault = true; + if (isOpen) { + toggleMenu(); + } } }, - } - : {}; - - // The input should be described by the appropriate message text id - // when both the message is supplied *and* when the component is in - // the matching state (invalid, warn, etc). - const ariaDescribedBy = - (invalid && invalidText && invalidTextId) || - (warn && warnText && warnTextId) || - (helperText && !isFluid && helperTextId) || - undefined; - - return ( -
- {titleText && ( - - {titleText} - - )} - -
- - {invalid && ( - - )} - {showWarning && ( - - )} - {inputValue && ( - - )} - -
- {normalizedSlug} - - {isOpen - ? filterItems(items, itemToString, inputValue).map( - (item, index) => { - const isObject = - item !== null && typeof item === 'object'; - const title = - isObject && 'text' in item && itemToElement - ? item.text?.toString() - : itemToString(item); - const disabled = - isObject && 'disabled' in item - ? !!item.disabled - : undefined; - const itemProps = getItemProps({ - item, - index, - ['aria-current']: - selectedItem === item ? 'true' : 'false', - ['aria-selected']: - highlightedIndex === index ? 'true' : 'false', - disabled, - }); - return ( - - {ItemToElement ? ( - - ) : ( - itemToString(item) - )} - {selectedItem === item && ( - - )} - - ); - } - ) - : null} - -
- {helperText && !invalid && !warn && !isFluid && ( - - {helperText} - - )} -
- ); - }} - + })} + {...rest} + {...readOnlyEventHandlers} + readOnly={readOnly} + aria-describedby={ariaDescribedBy} + /> + + {invalid && ( + + )} + {showWarning && ( + + )} + {inputValue && ( + { + selectItem(null); + }} + translateWithId={translateWithId} + disabled={disabled || readOnly} + onClearSelection={handleSelectionClear} + selectionCount={0} + /> + )} + +
+ {normalizedSlug} + + {isOpen + ? filterItems(items, itemToString, inputValue).map( + (item, index) => { + const isObject = item !== null && typeof item === 'object'; + const title = + isObject && 'text' in item && itemToElement + ? item.text?.toString() + : itemToString(item); + const itemProps = getItemProps({ + item, + index, + }); + + // The initial implementation using would place the disabled attribute + // on disabled menu items. Conversely, useCombobox places aria-disabled instead. + // To avoid any potential breaking changes, we avoid placing aria-disabled and + // instead match the old behavior of placing the disabled attribute. + const disabled = itemProps['aria-disabled']; + const { + 'aria-disabled': unusedAriaDisabled, // eslint-disable-line @typescript-eslint/no-unused-vars + ...modifiedItemProps + } = itemProps; + + return ( + + {ItemToElement ? ( + + ) : ( + itemToString(item) + )} + {selectedItem === item && ( + + )} + + ); + } + ) + : null} + +
+ {helperText && !invalid && !warn && !isFluid && ( + + {helperText} + + )} +
); } ); @@ -856,8 +840,9 @@ ComboBox.propTypes = { /** * Additional props passed to Downshift */ - // @ts-ignore - downshiftProps: PropTypes.shape(Downshift.propTypes), + downshiftProps: PropTypes.object as React.Validator< + UseComboboxProps + >, /** * Provide helper text that is used alongside the control label for * additional help diff --git a/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js b/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js index 7598fa7a5bc7..7d7647edcf65 100644 --- a/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js +++ b/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js @@ -21,7 +21,7 @@ const prefix = 'cds'; const findInputNode = () => screen.getByRole('combobox'); const openMenu = async () => { - await userEvent.click(findInputNode()); + await userEvent.click(screen.getByTitle('Open')); }; describe('FluidComboBox', () => { @@ -55,7 +55,7 @@ describe('FluidComboBox', () => { it('should display the menu of items when a user clicks on the input', async () => { render(); - await userEvent.click(findInputNode()); + await openMenu(); assertMenuOpen(mockProps); }); diff --git a/packages/react/src/components/ListBox/next/ListBoxTrigger.js b/packages/react/src/components/ListBox/next/ListBoxTrigger.js index 9ca2c0598090..996f97f6cfcf 100644 --- a/packages/react/src/components/ListBox/next/ListBoxTrigger.js +++ b/packages/react/src/components/ListBox/next/ListBoxTrigger.js @@ -27,6 +27,7 @@ const defaultTranslateWithId = (id) => defaultTranslations[id]; * `ListBoxTrigger` is used to orient the icon up or down depending on the * state of the menu for a given `ListBox` */ + const ListBoxTrigger = React.forwardRef(function ListBoxTrigger( { isOpen, translateWithId: t = defaultTranslateWithId, ...rest }, ref