diff --git a/packages/@react-spectrum/s2/test/Picker.test.tsx b/packages/@react-spectrum/s2/test/Picker.test.tsx index 20192fd6b39..05d59184f1e 100644 --- a/packages/@react-spectrum/s2/test/Picker.test.tsx +++ b/packages/@react-spectrum/s2/test/Picker.test.tsx @@ -180,40 +180,6 @@ describe('Picker', () => { expect(tree.getByTestId('custom-value')).toHaveTextContent('Chocolate, Vanilla'); }); - it('supports shift+click to select a range in multi-selection', async () => { - let user = userEvent.setup({delay: null, pointerMap}); - let items = [ - {id: 'chocolate', name: 'Chocolate'}, - {id: 'strawberry', name: 'Strawberry'}, - {id: 'vanilla', name: 'Vanilla'} - ]; - let tree = render( - - {(item: any) => ( - - {item.name} - - )} - - ); - - let selectTester = testUtilUser.createTester('Select', { - root: tree.container, - interactionType: 'mouse' - }); - await selectTester.open(); - let options = selectTester.getOptions(); - - await user.click(options[0]); - await user.keyboard('{Shift>}'); - await user.click(options[2]); - await user.keyboard('{/Shift}'); - - expect(options[0]).toHaveAttribute('aria-selected', 'true'); - expect(options[1]).toHaveAttribute('aria-selected', 'true'); - expect(options[2]).toHaveAttribute('aria-selected', 'true'); - }); - it('should warn if the custom render value output has a interactive child', async () => { using spy = jest.spyOn(console, 'warn').mockImplementation(() => {}) as jest.SpyInstance & Disposable; diff --git a/packages/react-aria-components/test/Select.test.js b/packages/react-aria-components/test/Select.test.js index 56e6ac0a251..df4906037d2 100644 --- a/packages/react-aria-components/test/Select.test.js +++ b/packages/react-aria-components/test/Select.test.js @@ -846,50 +846,6 @@ describe('Select', () => { expect(trigger).toHaveTextContent('2 selected items'); }); - it('supports shift+click to select a range in multi-selection', async () => { - let {getByTestId} = render(); - let selectTester = testUtilUser.createTester('Select', {root: getByTestId('select')}); - - await selectTester.open(); - let options = selectTester.getOptions(); - - await user.click(options[0]); - expect(options[0]).toHaveAttribute('aria-selected', 'true'); - - await user.keyboard('{Shift>}'); - await user.click(options[2]); - await user.keyboard('{/Shift}'); - - expect(options[0]).toHaveAttribute('aria-selected', 'true'); - expect(options[1]).toHaveAttribute('aria-selected', 'true'); - expect(options[2]).toHaveAttribute('aria-selected', 'true'); - }); - - it('keeps a stable anchor across consecutive shift+clicks', async () => { - let {getByTestId} = render(); - let selectTester = testUtilUser.createTester('Select', {root: getByTestId('select')}); - - await selectTester.open(); - let options = selectTester.getOptions(); - - await user.click(options[0]); - - await user.keyboard('{Shift>}'); - await user.click(options[2]); - expect(options[0]).toHaveAttribute('aria-selected', 'true'); - expect(options[1]).toHaveAttribute('aria-selected', 'true'); - expect(options[2]).toHaveAttribute('aria-selected', 'true'); - - // Shift+click again from the same anchor: the range shrinks rather than the - // anchor jumping to the previous target. - await user.click(options[1]); - await user.keyboard('{/Shift}'); - - expect(options[0]).toHaveAttribute('aria-selected', 'true'); - expect(options[1]).toHaveAttribute('aria-selected', 'true'); - expect(options[2]).toHaveAttribute('aria-selected', 'false'); - }); - it('has a value immediately after rendering', async () => { function Example() { const ref = useRef(null); diff --git a/packages/react-stately/src/select/useSelectState.ts b/packages/react-stately/src/select/useSelectState.ts index adb6264a413..beb206ddc2c 100644 --- a/packages/react-stately/src/select/useSelectState.ts +++ b/packages/react-stately/src/select/useSelectState.ts @@ -29,7 +29,7 @@ import {FormValidationState, useFormValidationState} from '../form/useFormValida import {ListState, useListState} from '../list/useListState'; import {OverlayTriggerState, useOverlayTriggerState} from '../overlays/useOverlayTriggerState'; import {useControlledState} from '../utils/useControlledState'; -import {useMemo, useRef, useState} from 'react'; +import {useMemo, useState} from 'react'; export type SelectionMode = 'single' | 'multiple'; export type ValueType = M extends 'single' ? Key | null : readonly Key[]; @@ -196,27 +196,12 @@ export function useSelectState( } }; - // Preserve the selection's anchor (anchorKey/currentKey) across renders. The - // multiple-selection `value` is a plain Key[], so without this the listbox - // would rebuild an anchorless Selection on every render and range selection - // (shift+click / shift+arrow) would collapse to just the clicked item. We keep - // the last Selection produced internally and feed it back while its membership - // still matches `value`. - let lastSelection = useRef | null>(null); - let listState = useListState({ ...props, selectionMode, disallowEmptySelection: selectionMode === 'single', allowDuplicateSelectionEvents: true, - selectedKeys: useMemo(() => { - let selectedKeys = convertValue(displayValue); - let last = lastSelection.current; - if (last != null && Array.isArray(selectedKeys) && isSameSelection(last, selectedKeys)) { - return last; - } - return selectedKeys; - }, [displayValue]), + selectedKeys: useMemo(() => convertValue(displayValue), [displayValue]), onSelectionChange: (keys: Selection) => { // impossible, but TS doesn't know that if (keys === 'all') { @@ -227,9 +212,6 @@ export function useSelectState( let key = keys.values().next().value ?? null; setValue(key); } else { - // Remember the Selection (with its anchor) so it survives the round-trip - // through the plain `value` array on the next render. - lastSelection.current = keys; setValue([...keys]); } if (shouldCloseOnSelect) { @@ -296,15 +278,3 @@ function convertValue(value: Key | Key[] | null | undefined) { } return Array.isArray(value) ? value : [value]; } - -function isSameSelection(selection: Set, keys: Key[]): boolean { - if (selection.size !== keys.length) { - return false; - } - for (let key of keys) { - if (!selection.has(key)) { - return false; - } - } - return true; -}