From 6819cb46d49c84e2c4c7125c58ca7087d3272526 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Fri, 26 Jun 2026 12:05:28 +1000 Subject: [PATCH 1/2] fix: table resizing click stuck (#10040) * fix: table resizing click stuck * fix cursor styles * fix formatting * fix lint * alternative approach following v3 * lint * fix format * remove extra comment --- .../react-spectrum/src/table/Resizer.tsx | 26 +++---------------- packages/react-aria-components/src/Table.tsx | 24 +++++++---------- .../src/table/useTableColumnResize.ts | 19 +++++++++++--- 3 files changed, 28 insertions(+), 41 deletions(-) diff --git a/packages/@adobe/react-spectrum/src/table/Resizer.tsx b/packages/@adobe/react-spectrum/src/table/Resizer.tsx index 648e4720197..c3af2c5d954 100644 --- a/packages/@adobe/react-spectrum/src/table/Resizer.tsx +++ b/packages/@adobe/react-spectrum/src/table/Resizer.tsx @@ -8,7 +8,7 @@ import intlMessages from '../../intl/table/*.json'; import {isWebKit} from 'react-aria/private/utils/platform'; import {Key, RefObject} from '@react-types/shared'; import {mergeProps} from 'react-aria/mergeProps'; -import React, {createContext, ForwardedRef, useContext, useEffect, useState} from 'react'; +import React, {createContext, ForwardedRef, useContext} from 'react'; import ReactDOM from 'react-dom'; import styles from '@adobe/spectrum-css-temp/components/table/vars.css'; import {useLocale} from 'react-aria/I18nProvider'; @@ -63,27 +63,7 @@ export const Resizer = React.forwardRef(function Resizer( let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table'); let {direction} = useLocale(); - let [isPointerDown, setIsPointerDown] = useState(false); - useEffect(() => { - let setDown = e => { - if (e.pointerType === 'mouse') { - setIsPointerDown(true); - } - }; - let setUp = e => { - if (e.pointerType === 'mouse') { - setIsPointerDown(false); - } - }; - document.addEventListener('pointerdown', setDown, {capture: true}); - document.addEventListener('pointerup', setUp, {capture: true}); - return () => { - document.removeEventListener('pointerdown', setDown, {capture: true}); - document.removeEventListener('pointerup', setUp, {capture: true}); - }; - }, []); - - let {inputProps, resizerProps} = useTableColumnResize( + let {inputProps, resizerProps, isMouseResizing} = useTableColumnResize( mergeProps(props, { 'aria-label': stringFormatter.format('columnResizer'), isDisabled: isEmpty @@ -128,7 +108,7 @@ export const Resizer = React.forwardRef(function Resizer( role="presentation" className={classNames(styles, 'spectrum-Table-columnResizerPlaceholder')} /> - +
diff --git a/packages/react-aria-components/src/Table.tsx b/packages/react-aria-components/src/Table.tsx index 1385b0704e9..45eb1d488bf 100644 --- a/packages/react-aria-components/src/Table.tsx +++ b/packages/react-aria-components/src/Table.tsx @@ -1374,7 +1374,7 @@ export const ColumnResizer = forwardRef(function ColumnResizer( let {onResizeStart, onResize, onResizeEnd} = useContext(ResizableTableContainerContext)!; let {column, triggerRef} = useContext(ColumnResizerContext)!; let inputRef = useRef(null); - let {resizerProps, inputProps, isResizing} = useTableColumnResize( + let {resizerProps, inputProps, isResizing, isMouseResizing} = useTableColumnResize( { column, 'aria-label': props['aria-label'] || stringFormatter.format('tableResizer'), @@ -1425,24 +1425,15 @@ export const ColumnResizer = forwardRef(function ColumnResizer( } }); - let [isMouseDown, setMouseDown] = useState(false); - let onPointerDown = (e: PointerEvent) => { - if (e.pointerType === 'mouse') { - setMouseDown(true); - } - }; - - if (!isResizing && isMouseDown) { - setMouseDown(false); - } - let DOMProps = filterDOMProps(props, {global: true}); + // Cursor overlay is used to style the cursor against the entire screen. + // Do not turn off pointer events or the cursor will no longer be styled. return ( {isResizing && - isMouseDown && + isMouseResizing && ReactDOM.createPortal( -
, +
, document.body )} diff --git a/packages/react-aria/src/table/useTableColumnResize.ts b/packages/react-aria/src/table/useTableColumnResize.ts index caea29c6eca..e3c1ae89334 100644 --- a/packages/react-aria/src/table/useTableColumnResize.ts +++ b/packages/react-aria/src/table/useTableColumnResize.ts @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import {ChangeEvent, useCallback, useEffect, useRef} from 'react'; +import {ChangeEvent, useCallback, useEffect, useRef, useState} from 'react'; import {ColumnSize} from 'react-stately/useTableState'; import {DOMAttributes, FocusableElement, Key, RefObject} from '@react-types/shared'; import {focusSafely} from '../interactions/focusSafely'; @@ -38,6 +38,11 @@ export interface TableColumnResizeAria { resizerProps: DOMAttributes; /** Whether this column is currently being resized. */ isResizing: boolean; + /** + * Whether this column is currently being resized via a mouse drag (e.g. to render a cursor + * overlay). + */ + isMouseResizing: boolean; } export interface AriaTableColumnResizeProps { @@ -94,6 +99,9 @@ export function useTableColumnResize( let lastSize = useRef | null>(null); let wasFocusedOnResizeStart = useRef(false); let editModeEnabled = state.tableState.isKeyboardNavigationDisabled; + // Whether a mouse drag-resize is active. Set on the first move (not on press) so a cursor + // overlay only mounts during an actual drag. + let [isMouseResizing, setMouseResizing] = useState(false); let {direction} = useLocale(); @@ -172,8 +180,11 @@ export function useTableColumnResize( const columnResizeWidthRef = useRef(0); const {moveProps} = useMove({ - onMoveStart() { + onMoveStart(e) { columnResizeWidthRef.current = state.getColumnWidth(item.key); + if (e.pointerType === 'mouse') { + setMouseResizing(true); + } startResize(item); }, onMove(e) { @@ -196,6 +207,7 @@ export function useTableColumnResize( onMoveEnd(e) { let {pointerType} = e; columnResizeWidthRef.current = 0; + setMouseResizing(false); if (pointerType === 'mouse' || (pointerType === 'touch' && wasFocusedOnResizeStart.current)) { endResize(item); } @@ -332,6 +344,7 @@ export function useTableColumnResize( }, ariaProps ), - isResizing + isResizing, + isMouseResizing }; } From b1af29c967873e19580ffe5d25a38731102b1b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20=F0=9F=87=AC=F0=9F=87=AD?= Date: Fri, 26 Jun 2026 03:19:32 +0100 Subject: [PATCH 2/2] fix(react-aria-components): pass overlay id to PopoverContext in Dialogue (#9807) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(react-aria-components): pass overlay id to PopoverContext in DialogTrigger (#9800) * fix format * fix(react-aria-components): apply overlay id to element with role=dialog in DialogTrigger Send overlay id to both DialogContext and PopoverContext, and have Popover only apply the id when it has role="dialog" — so the id lands on whichever element is the dialog, whether that's a nested Dialog or the Popover itself. * fix(react-aria-components): add id to PopoverInnerProps to fix typecheck * fix(test-utils): remove unnecessary nested dialog lookup in DialogTester --------- Co-authored-by: Robert Snow --- packages/react-aria-components/src/Dialog.tsx | 1 + .../react-aria-components/src/Popover.tsx | 3 +++ .../test/Popover.test.js | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/packages/react-aria-components/src/Dialog.tsx b/packages/react-aria-components/src/Dialog.tsx index cd92cf30f06..0d78f95ef18 100644 --- a/packages/react-aria-components/src/Dialog.tsx +++ b/packages/react-aria-components/src/Dialog.tsx @@ -105,6 +105,7 @@ export function DialogTrigger(props: DialogTriggerProps): JSX.Element { { trigger: 'DialogTrigger', triggerRef: buttonRef, + id: overlayProps.id, 'aria-labelledby': overlayProps['aria-labelledby'] } ] diff --git a/packages/react-aria-components/src/Popover.tsx b/packages/react-aria-components/src/Popover.tsx index 3d1fa9fe874..17ae7e3d3d0 100644 --- a/packages/react-aria-components/src/Popover.tsx +++ b/packages/react-aria-components/src/Popover.tsx @@ -140,6 +140,7 @@ export interface PopoverRenderProps { } interface PopoverContextValue extends PopoverProps { + id?: string; /** Contexts to clear. */ clearContexts?: Context[]; } @@ -205,6 +206,7 @@ interface PopoverInnerProps extends AriaPopoverProps, RenderProps[]; + id?: string; } function PopoverInner({ @@ -318,6 +320,7 @@ function PopoverInner({ { expect(dialog).toBeInTheDocument(); expect(dialog.closest('.react-aria-Popover')).toHaveAttribute('data-trigger', 'DialogTrigger'); + expect(button).toHaveAttribute('aria-controls'); + expect(dialog).toHaveAttribute('id', button.getAttribute('aria-controls')); + await user.click(document.body); expect(dialog).not.toBeInTheDocument(); }); + it('applies overlay id to standalone popover', async () => { + let {getByRole} = render( + +