diff --git a/src/select/__tests__/connect-options.test.ts b/src/select/__tests__/connect-options.test.ts index ef9b9b878e..43df365cef 100644 --- a/src/select/__tests__/connect-options.test.ts +++ b/src/select/__tests__/connect-options.test.ts @@ -31,4 +31,34 @@ describe('connectOptionsByValue', () => { const result = connectOptionsByValue([{ option }], [{ value: '1' }]); expect(result[0]).toEqual({ option }); }); + + test('should return an empty array when there are no selected options', () => { + expect(connectOptionsByValue([{ option: { value: '1' } }], [])).toEqual([]); + }); + + test('should preserve the order of the selected options', () => { + const options = [{ option: { value: '1' } }, { option: { value: '2' } }, { option: { value: '3' } }]; + const result = connectOptionsByValue(options, [{ value: '3' }, { value: '1' }]); + expect(result).toEqual([{ option: { value: '3' } }, { option: { value: '1' } }]); + }); + + test('should skip parent options when matching by value', () => { + const child = { option: { value: '1' }, type: 'child' as const }; + const parent = { option: { value: '1' }, type: 'parent' as const }; + const result = connectOptionsByValue([parent, child], [{ value: '1' }]); + expect(result[0]).toBe(child); + }); + + test('should return the first matching option when multiple share the same value', () => { + const first = { option: { value: '1', label: 'first' } }; + const second = { option: { value: '1', label: 'second' } }; + const result = connectOptionsByValue([first, second], [{ value: '1' }]); + expect(result[0]).toBe(first); + }); + + test('should match options with an undefined value', () => { + const option = { option: { label: 'no value' } }; + const result = connectOptionsByValue([option], [{ label: 'no value' }]); + expect(result[0]).toBe(option); + }); }); diff --git a/src/select/utils/connect-options.ts b/src/select/utils/connect-options.ts index 86c2750edc..fc9a4acaf1 100644 --- a/src/select/utils/connect-options.ts +++ b/src/select/utils/connect-options.ts @@ -6,17 +6,19 @@ export const connectOptionsByValue = ( options: ReadonlyArray, selectedOptions: ReadonlyArray ): ReadonlyArray => { - return (selectedOptions || []).map(selectedOption => { - for (const dropdownOption of options) { - if ( - dropdownOption.type !== 'parent' && - (dropdownOption.option as OptionDefinition).value === selectedOption.value - ) { - return dropdownOption; + if (!selectedOptions || selectedOptions.length === 0) { + return []; + } + const optionByValue = new Map(); + for (const dropdownOption of options) { + if (dropdownOption.type !== 'parent') { + const value = (dropdownOption.option as OptionDefinition).value; + if (!optionByValue.has(value)) { + optionByValue.set(value, dropdownOption); } } - return { option: selectedOption }; - }); + } + return selectedOptions.map(selectedOption => optionByValue.get(selectedOption.value) ?? { option: selectedOption }); }; export const findOptionIndex = (options: ReadonlyArray, option: OptionDefinition) => { diff --git a/src/select/utils/use-select.ts b/src/select/utils/use-select.ts index 99cc16c3a1..f650a44cb2 100644 --- a/src/select/utils/use-select.ts +++ b/src/select/utils/use-select.ts @@ -76,6 +76,7 @@ export function useSelect({ const hasFilter = filteringType !== 'none' && !embedded; const activeRef = hasFilter ? filterRef : menuRef; const __selectedOptions = connectOptionsByValue(options, selectedOptions); + const __selectedOptionsSet = new Set(__selectedOptions); const __selectedValuesSet = selectedOptions.reduce((selectedValuesSet: Set, item: OptionDefinition) => { if (item.value) { selectedValuesSet.add(item.value); @@ -277,17 +278,17 @@ export function useSelect({ const isSelectAll = option.type === 'select-all'; const highlighted = option === highlightedOption; const groupState = isGroup(option.option) ? getGroupState(option.option) : undefined; - const selected = isSelectAll ? isAllSelected : __selectedOptions.indexOf(option) > -1 || !!groupState?.selected; + const selected = isSelectAll ? isAllSelected : __selectedOptionsSet.has(option) || !!groupState?.selected; const nextOption = options[index + 1]?.option; const isNextSelected = !!nextOption && isGroup(nextOption) ? getGroupState(nextOption).selected - : __selectedOptions.indexOf(options[index + 1]) > -1; + : __selectedOptionsSet.has(options[index + 1]); const previousOption = options[index - 1]?.option; const isPreviousSelected = !!previousOption && isGroup(previousOption) ? getGroupState(previousOption).selected - : __selectedOptions.indexOf(options[index - 1]) > -1; + : __selectedOptionsSet.has(options[index - 1]); const optionProps: any = { key: index, option, @@ -354,7 +355,7 @@ export function useSelect({ const highlightedGroupSelected = !!highlightedOption && isGroup(highlightedOption.option) && getGroupState(highlightedOption.option).selected; const announceSelected = - !!highlightedOption && (__selectedOptions.indexOf(highlightedOption) > -1 || highlightedGroupSelected); + !!highlightedOption && (__selectedOptionsSet.has(highlightedOption) || highlightedGroupSelected); return { isOpen,