From 72a1c5d4917f64779ab826653ec1eb64f49586a4 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Tue, 30 Sep 2025 09:50:53 -0700 Subject: [PATCH 1/3] fix: Ensure Tray always uses `box-sizing: content-box` so it isn't affected by global css resets (#8948) * chore: Fix nextJS test app so it doesnt cause trays to hide their contents * set a box-sizing: content-box to avoid css reset --- packages/@adobe/spectrum-css-temp/components/tray/index.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/@adobe/spectrum-css-temp/components/tray/index.css b/packages/@adobe/spectrum-css-temp/components/tray/index.css index 30f025ac6d2..0725c8790ca 100644 --- a/packages/@adobe/spectrum-css-temp/components/tray/index.css +++ b/packages/@adobe/spectrum-css-temp/components/tray/index.css @@ -55,7 +55,7 @@ /* Add padding at the bottom to account for the rest of the viewport height behind the keyboard. * This is necessary so that there isn't a visible gap that appears while the keyboard is animating * in and out. Fall back to the safe area inset to account for things like iOS home indicator. - We also add an additional 100vh of padding (offset by the bottom position below) so the tray + We also add an additional 100vh of padding (offset by the bottom position below) so the tray extends behind Safari's address bar and keyboard in iOS 26. */ padding-bottom: calc(max(calc(100dvh - var(--spectrum-visual-viewport-height)), env(safe-area-inset-bottom)) + 100vh); position: absolute; @@ -75,6 +75,8 @@ /* Exit animations */ transition: opacity var(--spectrum-dialog-exit-animation-duration) cubic-bezier(0.5, 0, 1, 1) var(--spectrum-dialog-exit-animation-delay), transform var(--spectrum-dialog-exit-animation-duration) cubic-bezier(0.5, 0, 1, 1) var(--spectrum-dialog-exit-animation-delay); + + box-sizing: content-box; } .is-open { From 836dc5d88f050b9419b1e31853b8e4e035198055 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Tue, 30 Sep 2025 10:01:25 -0700 Subject: [PATCH 2/3] fix: ComboBox item actions should not be selectable (#8947) * fix: ComboBox item actions should not be selectable * Add placeholder to example --- .../@react-aria/combobox/src/useComboBox.ts | 9 +++++-- .../@react-aria/listbox/src/useListBox.ts | 4 ++- packages/@react-aria/listbox/src/useOption.ts | 2 ++ .../selection/src/useSelectableItem.ts | 4 ++- .../s2/stories/ComboBox.stories.tsx | 1 + .../test/ComboBox.test.js | 25 +++++++++++++++++-- 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/@react-aria/combobox/src/useComboBox.ts b/packages/@react-aria/combobox/src/useComboBox.ts index 1b42aea1daf..5fe460012f0 100644 --- a/packages/@react-aria/combobox/src/useComboBox.ts +++ b/packages/@react-aria/combobox/src/useComboBox.ts @@ -16,7 +16,7 @@ import {AriaComboBoxProps} from '@react-types/combobox'; import {ariaHideOutside} from '@react-aria/overlays'; import {AriaListBoxOptions, getItemId, listData} from '@react-aria/listbox'; import {BaseEvent, DOMAttributes, KeyboardDelegate, LayoutDelegate, PressEvent, RefObject, RouterOptions, ValidationResult} from '@react-types/shared'; -import {chain, getActiveElement, getOwnerDocument, isAppleDevice, mergeProps, useLabels, useRouter, useUpdateEffect} from '@react-aria/utils'; +import {chain, getActiveElement, getOwnerDocument, isAppleDevice, mergeProps, useEvent, useLabels, useRouter, useUpdateEffect} from '@react-aria/utils'; import {ComboBoxState} from '@react-stately/combobox'; import {dispatchVirtualFocus} from '@react-aria/focus'; import {FocusEvent, InputHTMLAttributes, KeyboardEvent, TouchEvent, useEffect, useMemo, useRef} from 'react'; @@ -354,6 +354,10 @@ export function useComboBox(props: AriaComboBoxOptions, state: ComboBoxSta } }, [focusedItem]); + useEvent(listBoxRef, 'react-aria-item-action', state.isOpen ? () => { + state.close(); + } : undefined); + return { labelProps, buttonProps: { @@ -383,7 +387,8 @@ export function useComboBox(props: AriaComboBoxOptions, state: ComboBoxSta shouldUseVirtualFocus: true, shouldSelectOnPressUp: true, shouldFocusOnHover: true, - linkBehavior: 'selection' as const + linkBehavior: 'selection' as const, + ['UNSTABLE_itemBehavior']: 'action' }), descriptionProps, errorMessageProps, diff --git a/packages/@react-aria/listbox/src/useListBox.ts b/packages/@react-aria/listbox/src/useListBox.ts index 8774c71bab1..c5ac0984bbf 100644 --- a/packages/@react-aria/listbox/src/useListBox.ts +++ b/packages/@react-aria/listbox/src/useListBox.ts @@ -100,7 +100,9 @@ export function useListBox(props: AriaListBoxOptions, state: ListState, shouldFocusOnHover: props.shouldFocusOnHover, isVirtualized: props.isVirtualized, onAction: props.onAction, - linkBehavior + linkBehavior, + // @ts-ignore + UNSTABLE_itemBehavior: props['UNSTABLE_itemBehavior'] }); let {labelProps, fieldProps} = useLabel({ diff --git a/packages/@react-aria/listbox/src/useOption.ts b/packages/@react-aria/listbox/src/useOption.ts index a8412d1acb6..a2856a8e9dd 100644 --- a/packages/@react-aria/listbox/src/useOption.ts +++ b/packages/@react-aria/listbox/src/useOption.ts @@ -137,6 +137,8 @@ export function useOption(props: AriaOptionProps, state: ListState, ref: R isDisabled, onAction: onAction || item?.props?.onAction ? chain(item?.props?.onAction, onAction) : undefined, linkBehavior: data?.linkBehavior, + // @ts-ignore + UNSTABLE_itemBehavior: data?.['UNSTABLE_itemBehavior'], id }); diff --git a/packages/@react-aria/selection/src/useSelectableItem.ts b/packages/@react-aria/selection/src/useSelectableItem.ts index 3a892960cf9..cd6107a769b 100644 --- a/packages/@react-aria/selection/src/useSelectableItem.ts +++ b/packages/@react-aria/selection/src/useSelectableItem.ts @@ -205,8 +205,9 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte // With highlight selection, onAction is secondary, and occurs on double click. Single click selects the row. // With touch, onAction occurs on single tap, and long press enters selection mode. let isLinkOverride = manager.isLink(key) && linkBehavior === 'override'; + let isActionOverride = onAction && options['UNSTABLE_itemBehavior'] === 'action'; let hasLinkAction = manager.isLink(key) && linkBehavior !== 'selection' && linkBehavior !== 'none'; - let allowsSelection = !isDisabled && manager.canSelectItem(key) && !isLinkOverride; + let allowsSelection = !isDisabled && manager.canSelectItem(key) && !isLinkOverride && !isActionOverride; let allowsActions = (onAction || hasLinkAction) && !isDisabled; let hasPrimaryAction = allowsActions && ( manager.selectionBehavior === 'replace' @@ -225,6 +226,7 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte let performAction = (e) => { if (onAction) { onAction(); + ref.current?.dispatchEvent(new CustomEvent('react-aria-item-action', {bubbles: true})); } if (hasLinkAction && ref.current) { diff --git a/packages/@react-spectrum/s2/stories/ComboBox.stories.tsx b/packages/@react-spectrum/s2/stories/ComboBox.stories.tsx index b4132fab066..73dea75fd1c 100644 --- a/packages/@react-spectrum/s2/stories/ComboBox.stories.tsx +++ b/packages/@react-spectrum/s2/stories/ComboBox.stories.tsx @@ -318,6 +318,7 @@ export function WithCreateOption() { return ( {inputValue.length > 0 && ( diff --git a/packages/react-aria-components/test/ComboBox.test.js b/packages/react-aria-components/test/ComboBox.test.js index e9b1971a5da..b786271a0da 100644 --- a/packages/react-aria-components/test/ComboBox.test.js +++ b/packages/react-aria-components/test/ComboBox.test.js @@ -409,7 +409,7 @@ describe('ComboBox', () => { expect(options[0]).toHaveTextContent('No results'); }); - it('should support onAction', async () => { + it.each(['keyboard', 'mouse'])('should support onAction with %s', async (interactionType) => { let onAction = jest.fn(); function WithCreateOption() { let [inputValue, setInputValue] = useState(''); @@ -457,8 +457,29 @@ describe('ComboBox', () => { expect(options).toHaveLength(1); expect(options[0]).toHaveTextContent('Create "L"'); - await user.keyboard('{ArrowDown}{Enter}'); + if (interactionType === 'keyboard') { + await user.keyboard('{ArrowDown}{Enter}'); + } else { + await user.click(options[0]); + } expect(onAction).toHaveBeenCalledTimes(1); expect(comboboxTester.combobox).toHaveValue(''); + + // Repeat with an option selected. + await comboboxTester.selectOption({option: 'Cat'}); + + await user.keyboard('s'); + + options = comboboxTester.options(); + expect(options).toHaveLength(1); + expect(options[0]).toHaveTextContent('Create "Cats"'); + + if (interactionType === 'keyboard') { + await user.keyboard('{ArrowDown}{Enter}'); + } else { + await user.click(options[0]); + } + expect(onAction).toHaveBeenCalledTimes(2); + expect(comboboxTester.combobox).toHaveValue('Cat'); }); }); From 3e2e1908c2eb62663aa4963c97edac8086bd1f09 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Tue, 30 Sep 2025 11:24:13 -0700 Subject: [PATCH 3/3] chore: add missing placeholders to test apps and stories (#8949) * chore: add missing placeholders to test apps and stories * add placeholders to next app --------- Co-authored-by: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> --- examples/s2-next-macros/src/app/Lazy.js | 12 ++++++------ examples/s2-parcel-example/src/Lazy.js | 12 ++++++------ examples/s2-webpack-5-example/src/Lazy.js | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/s2-next-macros/src/app/Lazy.js b/examples/s2-next-macros/src/app/Lazy.js index cd4d1202b2a..b4c10079ddb 100644 --- a/examples/s2-next-macros/src/app/Lazy.js +++ b/examples/s2-next-macros/src/app/Lazy.js @@ -109,7 +109,7 @@ export default function Lazy() { return ( <>
- + @@ -153,16 +153,16 @@ export default function Lazy() { Baseball Basketball - + Dogs Cats Low power mode -