Skip to content

Commit 836dc5d

Browse files
authored
fix: ComboBox item actions should not be selectable (#8947)
* fix: ComboBox item actions should not be selectable * Add placeholder to example
1 parent 72a1c5d commit 836dc5d

File tree

6 files changed

+39
-6
lines changed

6 files changed

+39
-6
lines changed

packages/@react-aria/combobox/src/useComboBox.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {AriaComboBoxProps} from '@react-types/combobox';
1616
import {ariaHideOutside} from '@react-aria/overlays';
1717
import {AriaListBoxOptions, getItemId, listData} from '@react-aria/listbox';
1818
import {BaseEvent, DOMAttributes, KeyboardDelegate, LayoutDelegate, PressEvent, RefObject, RouterOptions, ValidationResult} from '@react-types/shared';
19-
import {chain, getActiveElement, getOwnerDocument, isAppleDevice, mergeProps, useLabels, useRouter, useUpdateEffect} from '@react-aria/utils';
19+
import {chain, getActiveElement, getOwnerDocument, isAppleDevice, mergeProps, useEvent, useLabels, useRouter, useUpdateEffect} from '@react-aria/utils';
2020
import {ComboBoxState} from '@react-stately/combobox';
2121
import {dispatchVirtualFocus} from '@react-aria/focus';
2222
import {FocusEvent, InputHTMLAttributes, KeyboardEvent, TouchEvent, useEffect, useMemo, useRef} from 'react';
@@ -354,6 +354,10 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
354354
}
355355
}, [focusedItem]);
356356

357+
useEvent(listBoxRef, 'react-aria-item-action', state.isOpen ? () => {
358+
state.close();
359+
} : undefined);
360+
357361
return {
358362
labelProps,
359363
buttonProps: {
@@ -383,7 +387,8 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
383387
shouldUseVirtualFocus: true,
384388
shouldSelectOnPressUp: true,
385389
shouldFocusOnHover: true,
386-
linkBehavior: 'selection' as const
390+
linkBehavior: 'selection' as const,
391+
['UNSTABLE_itemBehavior']: 'action'
387392
}),
388393
descriptionProps,
389394
errorMessageProps,

packages/@react-aria/listbox/src/useListBox.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ export function useListBox<T>(props: AriaListBoxOptions<T>, state: ListState<T>,
100100
shouldFocusOnHover: props.shouldFocusOnHover,
101101
isVirtualized: props.isVirtualized,
102102
onAction: props.onAction,
103-
linkBehavior
103+
linkBehavior,
104+
// @ts-ignore
105+
UNSTABLE_itemBehavior: props['UNSTABLE_itemBehavior']
104106
});
105107

106108
let {labelProps, fieldProps} = useLabel({

packages/@react-aria/listbox/src/useOption.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ export function useOption<T>(props: AriaOptionProps, state: ListState<T>, ref: R
137137
isDisabled,
138138
onAction: onAction || item?.props?.onAction ? chain(item?.props?.onAction, onAction) : undefined,
139139
linkBehavior: data?.linkBehavior,
140+
// @ts-ignore
141+
UNSTABLE_itemBehavior: data?.['UNSTABLE_itemBehavior'],
140142
id
141143
});
142144

packages/@react-aria/selection/src/useSelectableItem.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,9 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
205205
// With highlight selection, onAction is secondary, and occurs on double click. Single click selects the row.
206206
// With touch, onAction occurs on single tap, and long press enters selection mode.
207207
let isLinkOverride = manager.isLink(key) && linkBehavior === 'override';
208+
let isActionOverride = onAction && options['UNSTABLE_itemBehavior'] === 'action';
208209
let hasLinkAction = manager.isLink(key) && linkBehavior !== 'selection' && linkBehavior !== 'none';
209-
let allowsSelection = !isDisabled && manager.canSelectItem(key) && !isLinkOverride;
210+
let allowsSelection = !isDisabled && manager.canSelectItem(key) && !isLinkOverride && !isActionOverride;
210211
let allowsActions = (onAction || hasLinkAction) && !isDisabled;
211212
let hasPrimaryAction = allowsActions && (
212213
manager.selectionBehavior === 'replace'
@@ -225,6 +226,7 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
225226
let performAction = (e) => {
226227
if (onAction) {
227228
onAction();
229+
ref.current?.dispatchEvent(new CustomEvent('react-aria-item-action', {bubbles: true}));
228230
}
229231

230232
if (hasLinkAction && ref.current) {

packages/@react-spectrum/s2/stories/ComboBox.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ export function WithCreateOption() {
318318
return (
319319
<ComboBox
320320
label="Favorite Animal"
321+
placeholder="Select an animal"
321322
inputValue={inputValue}
322323
onInputChange={setInputValue}>
323324
{inputValue.length > 0 && (

packages/react-aria-components/test/ComboBox.test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ describe('ComboBox', () => {
409409
expect(options[0]).toHaveTextContent('No results');
410410
});
411411

412-
it('should support onAction', async () => {
412+
it.each(['keyboard', 'mouse'])('should support onAction with %s', async (interactionType) => {
413413
let onAction = jest.fn();
414414
function WithCreateOption() {
415415
let [inputValue, setInputValue] = useState('');
@@ -457,8 +457,29 @@ describe('ComboBox', () => {
457457
expect(options).toHaveLength(1);
458458
expect(options[0]).toHaveTextContent('Create "L"');
459459

460-
await user.keyboard('{ArrowDown}{Enter}');
460+
if (interactionType === 'keyboard') {
461+
await user.keyboard('{ArrowDown}{Enter}');
462+
} else {
463+
await user.click(options[0]);
464+
}
461465
expect(onAction).toHaveBeenCalledTimes(1);
462466
expect(comboboxTester.combobox).toHaveValue('');
467+
468+
// Repeat with an option selected.
469+
await comboboxTester.selectOption({option: 'Cat'});
470+
471+
await user.keyboard('s');
472+
473+
options = comboboxTester.options();
474+
expect(options).toHaveLength(1);
475+
expect(options[0]).toHaveTextContent('Create "Cats"');
476+
477+
if (interactionType === 'keyboard') {
478+
await user.keyboard('{ArrowDown}{Enter}');
479+
} else {
480+
await user.click(options[0]);
481+
}
482+
expect(onAction).toHaveBeenCalledTimes(2);
483+
expect(comboboxTester.combobox).toHaveValue('Cat');
463484
});
464485
});

0 commit comments

Comments
 (0)