From 6d1852977f3a587f5c1b9e13e60f9550bcc83d16 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 10 Apr 2026 14:46:20 +1000 Subject: [PATCH 1/4] fix: update expand hover behaviors --- packages/@react-spectrum/s2/src/TableView.tsx | 4 ++- packages/@react-spectrum/s2/src/TreeView.tsx | 4 ++- .../react-aria-components/test/Tree.test.tsx | 30 ----------------- .../test/Treeble.test.tsx | 33 ------------------- packages/react-aria/src/grid/useGridRow.ts | 3 +- .../src/gridlist/useGridListItem.ts | 7 +--- 6 files changed, 8 insertions(+), 73 deletions(-) diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 6dd2ef7b926..c7fefc7266c 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -1121,13 +1121,15 @@ interface ExpandableRowChevronProps { isExpanded?: boolean, isDisabled?: boolean, isRTL?: boolean, - isHidden?: boolean + isHidden?: boolean, + isHovered?: boolean } const expandButton = style({ gridArea: 'expand-button', color: { default: 'inherit', + isHovered: baseColor('neutral-subdued').isHovered, isDisabled: { default: 'disabled', forcedColors: 'GrayText' diff --git a/packages/@react-spectrum/s2/src/TreeView.tsx b/packages/@react-spectrum/s2/src/TreeView.tsx index 1515dd47384..2d3c41aa8db 100644 --- a/packages/@react-spectrum/s2/src/TreeView.tsx +++ b/packages/@react-spectrum/s2/src/TreeView.tsx @@ -521,13 +521,15 @@ interface ExpandableRowChevronProps { isDisabled?: boolean, isRTL?: boolean, scale: 'medium' | 'large', - isHidden?: boolean + isHidden?: boolean, + isHovered?: boolean } const expandButton = style({ gridArea: 'expand-button', color: { default: 'inherit', + isHovered: baseColor('neutral-subdued').isHovered, isDisabled: { default: 'disabled', forcedColors: 'GrayText' diff --git a/packages/react-aria-components/test/Tree.test.tsx b/packages/react-aria-components/test/Tree.test.tsx index d9028b55ddc..434ac46e58a 100644 --- a/packages/react-aria-components/test/Tree.test.tsx +++ b/packages/react-aria-components/test/Tree.test.tsx @@ -751,36 +751,6 @@ describe('Tree', () => { expect(onSelectionChange).toHaveBeenCalledTimes(0); }); - it('multi select should expand the row if anywhere on the row is clicked and there is no onAction provided', async () => { - let {getAllByRole} = render(); - let row = getAllByRole('row')[1]; - await user.hover(row); - expect(row).toHaveAttribute('data-hovered', 'true'); - - await user.click(row); - expect(row).toHaveAttribute('aria-expanded', 'true'); - }); - - it('single select should expand the row if anywhere on the row is clicked and there is no onAction provided', async () => { - let {getAllByRole} = render(); - let row = getAllByRole('row')[1]; - await user.hover(row); - expect(row).toHaveAttribute('data-hovered', 'true'); - - await user.click(row); - expect(row).toHaveAttribute('aria-expanded', 'true'); - }); - - it('no selection should expand the row if anywhere on the row is clicked and there is no onAction provided', async () => { - let {getAllByRole} = render(); - let row = getAllByRole('row')[1]; - await user.hover(row); - expect(row).toHaveAttribute('data-hovered', 'true'); - - await user.click(row); - expect(row).toHaveAttribute('aria-expanded', 'true'); - }); - it('should prevent Esc from clearing selection if escapeKeyBehavior is "none"', async () => { let {getAllByRole} = render(); diff --git a/packages/react-aria-components/test/Treeble.test.tsx b/packages/react-aria-components/test/Treeble.test.tsx index bd87825ea02..a85a8a0edfa 100644 --- a/packages/react-aria-components/test/Treeble.test.tsx +++ b/packages/react-aria-components/test/Treeble.test.tsx @@ -536,39 +536,6 @@ describe('Treeble', () => { expect(onSelectionChange).toHaveBeenLastCalledWith(new Set(['games', 'mario', 'tetris'])); }); - it('supports expansion on disabled items with no action in disabledBehavior="selection" multiple selection', async () => { - let tree = render(); - let tester = utils.createTester('Table', {root: tree.getByTestId('treeble')}); - - await user.hover(tester.rows[1]); - expect(tester.rows[1]).toHaveAttribute('data-hovered', 'true'); - - await user.click(tester.rows[1]); - expect(tester.rows[1]).toHaveAttribute('aria-expanded', 'true'); - }); - - it('supports expansion on disabled items with no action in disabledBehavior="selection" single selection', async () => { - let tree = render(); - let tester = utils.createTester('Table', {root: tree.getByTestId('treeble')}); - - await user.hover(tester.rows[1]); - expect(tester.rows[1]).toHaveAttribute('data-hovered', 'true'); - - await user.click(tester.rows[1]); - expect(tester.rows[1]).toHaveAttribute('aria-expanded', 'true'); - }); - - it('supports expansion on disabled items with no action in disabledBehavior="selection" no selection', async () => { - let tree = render(); - let tester = utils.createTester('Table', {root: tree.getByTestId('treeble')}); - - await user.hover(tester.rows[1]); - expect(tester.rows[1]).toHaveAttribute('data-hovered', 'true'); - - await user.click(tester.rows[1]); - expect(tester.rows[1]).toHaveAttribute('aria-expanded', 'true'); - }); - it('should support drag and drop', async () => { let tree = render(); let tester = utils.createTester('Table', {root: tree.getByRole('treegrid')}); diff --git a/packages/react-aria/src/grid/useGridRow.ts b/packages/react-aria/src/grid/useGridRow.ts index 2489d76f5c7..2e328341189 100644 --- a/packages/react-aria/src/grid/useGridRow.ts +++ b/packages/react-aria/src/grid/useGridRow.ts @@ -77,8 +77,7 @@ export function useGridRow, S extends GridState tableState.toggleKey(node.key); } } diff --git a/packages/react-aria/src/gridlist/useGridListItem.ts b/packages/react-aria/src/gridlist/useGridListItem.ts index 844dbaafbd2..c2ab564852c 100644 --- a/packages/react-aria/src/gridlist/useGridListItem.ts +++ b/packages/react-aria/src/gridlist/useGridListItem.ts @@ -102,12 +102,7 @@ export function useGridListItem(props: AriaGridListItemOptions, state: ListSt let children = state.collection.getChildren?.(node.key); hasChildRows = hasChildRows || [...(children ?? [])].length > 1; - if ( - onAction == null && - !hasLink && - hasChildRows && - ((state.disabledKeys.has(node.key) || node.props?.isDisabled) || - state.selectionManager.selectionMode === 'none')) { + if (onAction == null && !hasLink && state.selectionManager.selectionMode === 'none' && hasChildRows) { onAction = () => state.toggleKey(node.key); } From bd75e0f020ebae3eea5422fbaa192dad2cdf34b3 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Sat, 11 Apr 2026 08:34:34 +1000 Subject: [PATCH 2/4] revert story changes --- packages/@react-spectrum/s2/stories/TableView.stories.tsx | 2 +- packages/@react-spectrum/s2/stories/TreeView.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@react-spectrum/s2/stories/TableView.stories.tsx b/packages/@react-spectrum/s2/stories/TableView.stories.tsx index f37a8976606..2bc351080b2 100644 --- a/packages/@react-spectrum/s2/stories/TableView.stories.tsx +++ b/packages/@react-spectrum/s2/stories/TableView.stories.tsx @@ -1783,7 +1783,7 @@ export const TableWithNestedRows: StoryObj = { 5/22/1980 - + Applications Folder 4/7/2025 diff --git a/packages/@react-spectrum/s2/stories/TreeView.stories.tsx b/packages/@react-spectrum/s2/stories/TreeView.stories.tsx index d28392e2568..ed9aa94269c 100644 --- a/packages/@react-spectrum/s2/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/s2/stories/TreeView.stories.tsx @@ -81,7 +81,7 @@ const TreeExampleStatic = (args: TreeViewProps): ReactElement => (
From 5683811827def1ac797bcc19b04811d9141ff105 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Fri, 10 Apr 2026 16:53:53 -0700 Subject: [PATCH 3/4] remove disabled expandable row from docs example --- packages/dev/s2-docs/pages/s2/TableView.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev/s2-docs/pages/s2/TableView.mdx b/packages/dev/s2-docs/pages/s2/TableView.mdx index 176e17fe035..47c7adcdd9a 100644 --- a/packages/dev/s2-docs/pages/s2/TableView.mdx +++ b/packages/dev/s2-docs/pages/s2/TableView.mdx @@ -21,7 +21,7 @@ import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; Name @@ -975,4 +975,4 @@ function subscribe(fn) { ### EditableCell - \ No newline at end of file + From 2d1ad34887b00b2fb4aba594bad339e405f93f7b Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Fri, 10 Apr 2026 17:16:59 -0700 Subject: [PATCH 4/4] move disabledBehavior example --- packages/dev/s2-docs/pages/s2/TableView.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/dev/s2-docs/pages/s2/TableView.mdx b/packages/dev/s2-docs/pages/s2/TableView.mdx index 47c7adcdd9a..6b40b756d0e 100644 --- a/packages/dev/s2-docs/pages/s2/TableView.mdx +++ b/packages/dev/s2-docs/pages/s2/TableView.mdx @@ -14,14 +14,13 @@ export const description = 'Displays data in rows and columns, with row selectio {docs.exports.TableView.description} -```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode', 'overflowMode', 'density', 'isQuiet', 'disabledBehavior']} initialProps={{'aria-label': 'Files', selectionMode: 'multiple', 'treeColumn': 'name', disabledBehavior: 'selection'}} type="s2" +```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode', 'overflowMode', 'density', 'isQuiet']} initialProps={{'aria-label': 'Files', selectionMode: 'multiple', 'treeColumn': 'name'}} type="s2" "use client"; import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2/TableView'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; Name @@ -644,7 +643,7 @@ export default function EditableTable(props) { Use `selectionMode` to enable single or multiple selection, and `selectedKeys` (matching each row's `id`) to control the selected rows. Return an [ActionBar](ActionBar) from `renderActionBar` to handle bulk actions, and use `onAction` for row navigation. Disable rows with `isDisabled`. See the [selection guide](selection) for details. -```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode', 'disallowEmptySelection']} initialProps={{selectionMode: 'multiple'}} wide type="s2" +```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode', 'disallowEmptySelection', 'disabledBehavior']} initialProps={{selectionMode: 'multiple', disabledBehavior: 'selection'}} wide type="s2" "use client"; import {TableView, TableHeader, Column, TableBody, Row, Cell, type Selection} from '@react-spectrum/s2/TableView'; import {ActionBar, ActionButton} from '@react-spectrum/s2/ActionBar';