From ec0f952b8bf77ecc418b0c81ce7eeb149909c916 Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Wed, 20 May 2026 14:51:40 -0700 Subject: [PATCH 1/5] fix: enable keyboard scrolling in S2 Popover when content overflows --- packages/@react-spectrum/s2/src/Popover.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index 6ead949d693..5eef1255cc3 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -98,12 +98,13 @@ let popover = style( default: 'elevated', isArrowShown: 'none' }, - outlineStyle: 'solid', - outlineWidth: 1, - outlineColor: { + borderStyle: 'solid', + borderWidth: 1, + borderColor: { default: lightDark('transparent-white-25', 'gray-200'), forcedColors: 'ButtonBorder' }, + outlineStyle: 'none', width: { size: { // Copied from designs, not sure if correct. @@ -154,7 +155,8 @@ let popover = style( isolation: 'isolate', pointerEvents: { isExiting: 'none' - } + }, + overflow: 'auto' }, getAllowedOverrides() ); @@ -322,10 +324,10 @@ const innerDivStyle = style( boxSizing: 'border-box', outlineStyle: 'none', borderRadius: 'inherit', - overflow: 'auto', + // overflow: 'auto', position: 'relative', width: 'full', - maxSize: 'inherit' + maxSize: 'inherit', }, getAllowedOverrides({height: true}) ); From 7657f46d9cff3fd33e256792cb7522145f61cc7d Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Wed, 20 May 2026 14:52:03 -0700 Subject: [PATCH 2/5] fix formatting --- packages/@react-spectrum/s2/src/Popover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index 5eef1255cc3..a2e611aed9b 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -327,7 +327,7 @@ const innerDivStyle = style( // overflow: 'auto', position: 'relative', width: 'full', - maxSize: 'inherit', + maxSize: 'inherit' }, getAllowedOverrides({height: true}) ); From 58b074e4fd732c3975d1b49b23f878c6cfeaf1a8 Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Wed, 20 May 2026 15:02:10 -0700 Subject: [PATCH 3/5] cleanup --- packages/@react-spectrum/s2/src/Popover.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index a2e611aed9b..4eec2d1cbb3 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -324,7 +324,6 @@ const innerDivStyle = style( boxSizing: 'border-box', outlineStyle: 'none', borderRadius: 'inherit', - // overflow: 'auto', position: 'relative', width: 'full', maxSize: 'inherit' From a0b1edea4625444d2058fb6e9d10ba630bdf85ec Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:34:39 -0700 Subject: [PATCH 4/5] move back to outline to prevent layout shift --- packages/@react-spectrum/s2/src/Popover.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index 4eec2d1cbb3..a2b921225ed 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -98,13 +98,12 @@ let popover = style( default: 'elevated', isArrowShown: 'none' }, - borderStyle: 'solid', - borderWidth: 1, - borderColor: { + outlineStyle: 'solid', + outlineWidth: 1, + outlineColor: { default: lightDark('transparent-white-25', 'gray-200'), forcedColors: 'ButtonBorder' }, - outlineStyle: 'none', width: { size: { // Copied from designs, not sure if correct. From 9d3725b264ad2d01c2ea2b09e8fa8f4a4eac9dbc Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:06:16 -0700 Subject: [PATCH 5/5] a slightly different solution --- packages/@react-spectrum/s2/src/Popover.tsx | 31 ++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index a2b921225ed..d462981d9f4 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -29,6 +29,8 @@ import { } from 'react'; import {DialogProps, OverlayTriggerStateContext} from 'react-aria-components/Dialog'; import {DOMRef, DOMRefValue, GlobalDOMAttributes} from '@react-types/shared'; +import {focusSafely} from 'react-aria/private/interactions/focusSafely'; +import {getActiveElement, nodeContains} from 'react-aria/private/utils/shadowdom/DOMFunctions'; import { getAllowedOverrides, heightProperties, @@ -155,7 +157,9 @@ let popover = style( pointerEvents: { isExiting: 'none' }, - overflow: 'auto' + overflow: { + isArrowHidden: 'auto' + } }, getAllowedOverrides() ); @@ -254,7 +258,8 @@ export const PopoverBase = forwardRef(function PopoverBase( size, isArrowShown: !hideArrow, colorScheme, - isSubmenu: renderProps.trigger === 'SubmenuTrigger' + isSubmenu: renderProps.trigger === 'SubmenuTrigger', + isArrowHidden: hideArrow }), styles ) @@ -325,7 +330,10 @@ const innerDivStyle = style( borderRadius: 'inherit', position: 'relative', width: 'full', - maxSize: 'inherit' + maxSize: 'inherit', + overflow: { + isArrowShown: 'auto' + } }, getAllowedOverrides({height: true}) ); @@ -341,12 +349,27 @@ export const Popover = forwardRef(function Popover( let domRef = useDOMRef(ref); let {UNSAFE_className, UNSAFE_style, styles, padding = 'default', ...otherProps} = props; + // Fires each time the inner div is inserted into the DOM (i.e. + // each time the popover opens). By the time RAC's useEffect checks + // isFocusWithin on the outer container, focus is already here, so RAC + // leaves it alone. + let innerRef = useCallback((el: HTMLDivElement | null) => { + if (el && !nodeContains(el, getActiveElement(document))) { + focusSafely(el); + } + }, []); + return ( {composeRenderProps(props.children, children => (
+ className={ + (UNSAFE_className || '') + + innerDivStyle({padding, isArrowShown: !otherProps.hideArrow}, styles) + }> {/* Reset OverlayTriggerStateContext so the buttons inside the dialog don't retain their hover state. */} {children}