From f22da5a696bd891a58aa0fc4c81f0fbe0030ba5d Mon Sep 17 00:00:00 2001 From: Guilherme Datilio Ribeiro Date: Thu, 1 Aug 2024 05:00:15 -0300 Subject: [PATCH] Fixed scrollIntoView in `Combobox` and `Dropdown` (#17074) * fix: fixed scrollIntoView * fix: fixed code to use onHighlightedIndexChange * fix: fixed const name * test: fixed tests --- .../src/components/ComboBox/ComboBox-test.js | 2 +- .../src/components/ComboBox/ComboBox.tsx | 17 +++++++- .../src/components/Dropdown/Dropdown-test.js | 1 + .../components/Dropdown/Dropdown.stories.js | 9 ++++ .../src/components/Dropdown/Dropdown.tsx | 42 +++++++++---------- .../__tests__/FluidComboBox-test.js | 2 +- .../__tests__/FluidDropdown-test.js | 1 + 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/react/src/components/ComboBox/ComboBox-test.js b/packages/react/src/components/ComboBox/ComboBox-test.js index 90a96109ed41..726d7a23f0fa 100644 --- a/packages/react/src/components/ComboBox/ComboBox-test.js +++ b/packages/react/src/components/ComboBox/ComboBox-test.js @@ -29,7 +29,7 @@ const prefix = 'cds'; describe('ComboBox', () => { let mockProps; - + window.HTMLElement.prototype.scrollIntoView = function () {}; beforeEach(() => { mockProps = { id: 'test-combobox', diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 6311b934f610..820de13197ee 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -461,6 +461,7 @@ const ComboBox = forwardRef( (state, actionAndChanges) => { const { type, changes } = actionAndChanges; const { highlightedIndex } = changes; + switch (type) { case InputBlur: if ( @@ -607,11 +608,23 @@ const ComboBox = forwardRef( setInputValue(inputValue || ''); setHighlightedIndex(indexToHighlight(inputValue)); }, - onSelectedItemChange({ selectedItem }) { onChange({ selectedItem }); }, - + onHighlightedIndexChange: ({ highlightedIndex }) => { + if (highlightedIndex! > -1 && typeof window !== undefined) { + const itemArray = document.querySelectorAll( + `li.${prefix}--list-box__menu-item[role="option"]` + ); + const highlightedItem = itemArray[highlightedIndex!]; + if (highlightedItem) { + highlightedItem.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + } + } + }, initialSelectedItem: initialSelectedItem, inputId: id, stateReducer, diff --git a/packages/react/src/components/Dropdown/Dropdown-test.js b/packages/react/src/components/Dropdown/Dropdown-test.js index e1cf0c25e604..b2b6f9c90fb2 100644 --- a/packages/react/src/components/Dropdown/Dropdown-test.js +++ b/packages/react/src/components/Dropdown/Dropdown-test.js @@ -193,6 +193,7 @@ describe('Dropdown', () => { }); describe('Component API', () => { + window.HTMLElement.prototype.scrollIntoView = function () {}; it('should accept a `ref` for the underlying button element', async () => { const ref = React.createRef(); render(); diff --git a/packages/react/src/components/Dropdown/Dropdown.stories.js b/packages/react/src/components/Dropdown/Dropdown.stories.js index 394916ab7799..ef1cc98ef4ce 100644 --- a/packages/react/src/components/Dropdown/Dropdown.stories.js +++ b/packages/react/src/components/Dropdown/Dropdown.stories.js @@ -67,6 +67,15 @@ const items = [ { text: 'Option 5', }, + { + text: 'Option 6', + }, + { + text: 'Option 7', + }, + { + text: 'Option 8', + }, ]; export const ExperimentalAutoAlign = () => ( diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index d2bebfae15a6..7441a55b0c4c 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -49,16 +49,10 @@ import { size as floatingSize, } from '@floating-ui/react'; -const { - ToggleButtonKeyDownArrowDown, - ToggleButtonKeyDownArrowUp, - ToggleButtonKeyDownHome, - ToggleButtonKeyDownEnd, - ItemMouseMove, - MenuMouseLeave, -} = useSelect.stateChangeTypes as UseSelectInterface['stateChangeTypes'] & { - ToggleButtonClick: UseSelectStateChangeTypes.ToggleButtonClick; -}; +const { ItemMouseMove, MenuMouseLeave } = + useSelect.stateChangeTypes as UseSelectInterface['stateChangeTypes'] & { + ToggleButtonClick: UseSelectStateChangeTypes.ToggleButtonClick; + }; const defaultItemToString = (item?: ItemType | null): string => { if (typeof item === 'string') { @@ -326,25 +320,27 @@ const Dropdown = React.forwardRef( const isObject = item !== null && typeof item === 'object'; return isObject && 'disabled' in item && item.disabled === true; }, + onHighlightedIndexChange: ({ highlightedIndex }) => { + if (highlightedIndex! > -1 && typeof window !== undefined) { + const itemArray = document.querySelectorAll( + `li.${prefix}--list-box__menu-item[role="option"]` + ); + const highlightedItem = itemArray[highlightedIndex!]; + if (highlightedItem) { + highlightedItem.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + } + } + }, }; const dropdownInstanceId = useId(); function stateReducer(state, actionAndChanges) { - const { changes, props, type } = actionAndChanges; - const { highlightedIndex } = changes; + const { changes, type } = actionAndChanges; switch (type) { - case ToggleButtonKeyDownArrowDown: - case ToggleButtonKeyDownArrowUp: - case ToggleButtonKeyDownHome: - case ToggleButtonKeyDownEnd: - if (highlightedIndex > -1) { - const itemArray = document.querySelectorAll( - `li.${prefix}--list-box__menu-item[role="option"]` - ); - props.scrollIntoView(itemArray[highlightedIndex]); - } - return changes; case ItemMouseMove: case MenuMouseLeave: return { ...changes, highlightedIndex: state.highlightedIndex }; diff --git a/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js b/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js index 94abc2dcfe68..976a1ae6b7eb 100644 --- a/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js +++ b/packages/react/src/components/FluidComboBox/__tests__/FluidComboBox-test.js @@ -27,7 +27,7 @@ const openMenu = async () => { describe('FluidComboBox', () => { let mockProps; - + window.HTMLElement.prototype.scrollIntoView = function () {}; beforeEach(() => { mockProps = { id: 'test-fluidcombobox', diff --git a/packages/react/src/components/FluidDropdown/__tests__/FluidDropdown-test.js b/packages/react/src/components/FluidDropdown/__tests__/FluidDropdown-test.js index 6edb4c8f2fb5..6ca60f290059 100644 --- a/packages/react/src/components/FluidDropdown/__tests__/FluidDropdown-test.js +++ b/packages/react/src/components/FluidDropdown/__tests__/FluidDropdown-test.js @@ -177,6 +177,7 @@ describe('FluidDropdown', () => { }); describe('Component API', () => { + window.HTMLElement.prototype.scrollIntoView = function () {}; it('should accept a `ref` for the underlying button element', async () => { const ref = React.createRef(); render();