Skip to content

Commit d7fc932

Browse files
authored
Add renderProp support for if a Table row has visible focus within it (#6350)
* Add renderProp support for knowing if a item of a row has visible focus useful for making a row style that remains applied when anything in the table row is focused, not just the row itself * add focusVisibleWithin to table cell as well * Revert "add focusVisibleWithin to table cell as well" This reverts commit 1aacf8f. * Add data id support to ColumnResizer this is for s2, make keyboard skip the resizer via data-react-aria-prevent-focus
1 parent 2bda4c9 commit d7fc932

File tree

2 files changed

+47
-12
lines changed

2 files changed

+47
-12
lines changed

packages/react-aria-components/src/Table.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ export const TableHeader = /*#__PURE__*/ createBranchComponent(
497497
}
498498
}, [])
499499
});
500-
500+
501501
let {rowGroupProps} = useTableRowGroup();
502502
return (
503503
<thead
@@ -792,6 +792,7 @@ function ColumnResizer(props: ColumnResizerProps, ref: ForwardedRef<HTMLDivEleme
792792
<div
793793
ref={objectRef}
794794
role="presentation"
795+
{...filterDOMProps(props as any)}
795796
{...renderProps}
796797
{...mergeProps(resizerProps, {onPointerDown}, hoverProps)}
797798
data-hovered={isHovered || undefined}
@@ -876,7 +877,10 @@ export const TableBody = /*#__PURE__*/ createBranchComponent('tablebody', <T ext
876877
);
877878
});
878879

879-
export interface RowRenderProps extends ItemRenderProps {}
880+
export interface RowRenderProps extends ItemRenderProps {
881+
/** Whether the row's children have keyboard focus. */
882+
isFocusVisibleWithin: boolean
883+
}
880884

881885
export interface RowProps<T> extends StyleRenderProps<RowRenderProps>, LinkDOMProps, HoverEvents {
882886
/** The unique id of the row. */
@@ -918,23 +922,27 @@ export const Row = /*#__PURE__*/ createBranchComponent(
918922
ref
919923
);
920924
let {isFocused, isFocusVisible, focusProps} = useFocusRing();
925+
let {
926+
isFocusVisible: isFocusVisibleWithin,
927+
focusProps: focusWithinProps
928+
} = useFocusRing({within: true});
921929
let {hoverProps, isHovered} = useHover({
922930
isDisabled: !states.allowsSelection && !states.hasAction,
923931
onHoverStart: props.onHoverStart,
924932
onHoverChange: props.onHoverChange,
925933
onHoverEnd: props.onHoverEnd
926934
});
927-
935+
928936
let {checkboxProps} = useTableSelectionCheckbox(
929937
{key: item.key},
930938
state
931939
);
932-
940+
933941
let draggableItem: DraggableItemResult | undefined = undefined;
934942
if (dragState && dragAndDropHooks) {
935943
draggableItem = dragAndDropHooks.useDraggableItem!({key: item.key, hasDragButton: true}, dragState);
936944
}
937-
945+
938946
let dropIndicator: DropIndicatorAria | undefined = undefined;
939947
let dropIndicatorRef = useRef<HTMLDivElement>(null);
940948
let {visuallyHiddenProps} = useVisuallyHidden();
@@ -943,7 +951,7 @@ export const Row = /*#__PURE__*/ createBranchComponent(
943951
target: {type: 'item', key: item.key, dropPosition: 'on'}
944952
}, dropState, dropIndicatorRef);
945953
}
946-
954+
947955
let renderDropIndicator = dragAndDropHooks?.renderDropIndicator || (target => <DropIndicator target={target} />);
948956
let dragButtonRef = useRef<HTMLButtonElement>(null);
949957
useEffect(() => {
@@ -952,7 +960,7 @@ export const Row = /*#__PURE__*/ createBranchComponent(
952960
}
953961
// eslint-disable-next-line
954962
}, []);
955-
963+
956964
let isDragging = dragState && dragState.isDragging(item.key);
957965
// eslint-disable-next-line @typescript-eslint/no-unused-vars
958966
let {children: _, ...restProps} = props;
@@ -968,10 +976,11 @@ export const Row = /*#__PURE__*/ createBranchComponent(
968976
selectionMode: state.selectionManager.selectionMode,
969977
selectionBehavior: state.selectionManager.selectionBehavior,
970978
isDragging,
971-
isDropTarget: dropIndicator?.isDropTarget
979+
isDropTarget: dropIndicator?.isDropTarget,
980+
isFocusVisibleWithin
972981
}
973982
});
974-
983+
975984
return (
976985
<>
977986
{dragAndDropHooks?.useDropIndicator &&
@@ -985,7 +994,7 @@ export const Row = /*#__PURE__*/ createBranchComponent(
985994
</tr>
986995
)}
987996
<tr
988-
{...mergeProps(filterDOMProps(props as any), rowProps, focusProps, hoverProps, draggableItem?.dragProps)}
997+
{...mergeProps(filterDOMProps(props as any), rowProps, focusProps, hoverProps, draggableItem?.dragProps, focusWithinProps)}
989998
{...renderProps}
990999
ref={ref}
9911000
data-disabled={states.isDisabled || undefined}
@@ -996,7 +1005,8 @@ export const Row = /*#__PURE__*/ createBranchComponent(
9961005
data-pressed={states.isPressed || undefined}
9971006
data-dragging={isDragging || undefined}
9981007
data-drop-target={dropIndicator?.isDropTarget || undefined}
999-
data-selection-mode={state.selectionManager.selectionMode === 'none' ? undefined : state.selectionManager.selectionMode}>
1008+
data-selection-mode={state.selectionManager.selectionMode === 'none' ? undefined : state.selectionManager.selectionMode}
1009+
data-focus-visible-within={isFocusVisibleWithin || undefined}>
10001010
<Provider
10011011
values={[
10021012
[CheckboxContext, {

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function MyColumn(props) {
2727
{sortDirection === 'ascending' ? '▲' : '▼'}
2828
</span>
2929
)}
30-
{props.allowsResizing && <ColumnResizer />}
30+
{props.allowsResizing && <ColumnResizer data-testid="resizer" />}
3131
</>)}
3232
</Column>
3333
);
@@ -769,6 +769,25 @@ describe('Table', () => {
769769
expect(onScroll).toHaveBeenCalled();
770770
});
771771

772+
it('should support data-focus-visible-within', async () => {
773+
let {getAllByRole} = renderTable();
774+
let items = getAllByRole('row');
775+
expect(items[1]).not.toHaveAttribute('data-focus-visible-within', 'true');
776+
777+
await user.tab();
778+
expect(document.activeElement).toBe(items[1]);
779+
expect(items[1]).toHaveAttribute('data-focus-visible-within', 'true');
780+
await user.keyboard('{ArrowRight}');
781+
782+
let cell = within(items[1]).getAllByRole('rowheader')[0];
783+
expect(document.activeElement).toBe(cell);
784+
expect(cell).toHaveAttribute('data-focus-visible', 'true');
785+
expect(items[1]).toHaveAttribute('data-focus-visible-within', 'true');
786+
787+
await user.keyboard('{ArrowDown}');
788+
expect(items[1]).not.toHaveAttribute('data-focus-visible-within', 'true');
789+
});
790+
772791
describe('drag and drop', () => {
773792
it('should support drag button slot', () => {
774793
let {getAllByRole} = render(<DraggableTable />);
@@ -1003,6 +1022,12 @@ describe('Table', () => {
10031022
</ResizableTableContainer>
10041023
);
10051024
}
1025+
1026+
it('Column resizer accepts data attributes', () => {
1027+
let {getAllByTestId} = render(<ControlledResizableTable />);
1028+
let resizers = getAllByTestId('resizer');
1029+
expect(resizers).toHaveLength(5);
1030+
});
10061031
});
10071032

10081033
it('should support overriding table style', () => {

0 commit comments

Comments
 (0)