diff --git a/packages/@adobe/spectrum-css-temp/components/table/skin.css b/packages/@adobe/spectrum-css-temp/components/table/skin.css index 1c20d9b89b7..67f307319ee 100644 --- a/packages/@adobe/spectrum-css-temp/components/table/skin.css +++ b/packages/@adobe/spectrum-css-temp/components/table/skin.css @@ -41,7 +41,7 @@ governing permissions and limitations under the License. } } - &:active { + &.is-active { color: var(--spectrum-table-header-text-color-down); .spectrum-Table-sortedIcon { @@ -77,6 +77,10 @@ governing permissions and limitations under the License. border-color: var(--spectrum-table-border-color); background-color: var(--spectrum-table-background-color); + &:focus-ring { + box-shadow: inset 0 0 0 2px var(--spectrum-table-cell-border-color-key-focus); + } + &.is-drop-target { border-color: var(--spectrum-alias-border-color-focus); box-shadow: 0 0 0 1px var(--spectrum-alias-border-color-focus); diff --git a/packages/@react-aria/grid/src/useGridCell.ts b/packages/@react-aria/grid/src/useGridCell.ts index 5f5705511d3..312e1765aeb 100644 --- a/packages/@react-aria/grid/src/useGridCell.ts +++ b/packages/@react-aria/grid/src/useGridCell.ts @@ -93,7 +93,8 @@ export function useGridCell>(props: GridCellProps isVirtualized, focus, shouldSelectOnPressUp, - onAction: onCellAction ? () => onCellAction(node.key) : onAction + onAction: onCellAction ? () => onCellAction(node.key) : onAction, + isDisabled: state.collection.size === 0 }); let onKeyDownCapture = (e: ReactKeyboardEvent) => { diff --git a/packages/@react-aria/grid/src/useGridRow.ts b/packages/@react-aria/grid/src/useGridRow.ts index 0785d3b453f..7ade74fd97f 100644 --- a/packages/@react-aria/grid/src/useGridRow.ts +++ b/packages/@react-aria/grid/src/useGridRow.ts @@ -59,7 +59,8 @@ export function useGridRow, S extends GridState onRowAction(node.key) : onAction + onAction: onRowAction ? () => onRowAction(node.key) : onAction, + isDisabled: state.collection.size === 0 }); let isSelected = state.selectionManager.isSelected(node.key); diff --git a/packages/@react-aria/gridlist/src/useGridList.ts b/packages/@react-aria/gridlist/src/useGridList.ts index 235bea3e7c3..916d509c363 100644 --- a/packages/@react-aria/gridlist/src/useGridList.ts +++ b/packages/@react-aria/gridlist/src/useGridList.ts @@ -78,7 +78,7 @@ export function useGridList(props: AriaGridListOptions, state: ListState(props: AriaTableProps, state: TableState, ref: gridProps: mergeProps( gridProps, descriptionProps, + // If table is empty, make sure the table is tabbable + state.collection.size === 0 && {tabIndex: 0}, { // merge sort description with long press information 'aria-describedby': [descriptionProps['aria-describedby'], gridProps['aria-describedby']].filter(Boolean).join(' ') diff --git a/packages/@react-aria/table/src/useTableColumnHeader.ts b/packages/@react-aria/table/src/useTableColumnHeader.ts index 6b59c04d7e2..efbae0ed932 100644 --- a/packages/@react-aria/table/src/useTableColumnHeader.ts +++ b/packages/@react-aria/table/src/useTableColumnHeader.ts @@ -90,7 +90,14 @@ export function useTableColumnHeader(props: AriaTableColumnHeaderProps, state return { columnHeaderProps: { - ...mergeProps(gridCellProps, pressProps, focusableProps, descriptionProps), + ...mergeProps( + gridCellProps, + pressProps, + focusableProps, + descriptionProps, + // If the table is empty, make all column headers untabbable or programatically focusable + state.collection.size === 0 && {tabIndex: null} + ), role: 'columnheader', id: getColumnHeaderId(state, node.key), 'aria-colspan': node.colspan && node.colspan > 1 ? node.colspan : null, diff --git a/packages/@react-aria/table/src/useTableColumnResize.ts b/packages/@react-aria/table/src/useTableColumnResize.ts index 5adfb7c51c2..7054c98a53a 100644 --- a/packages/@react-aria/table/src/useTableColumnResize.ts +++ b/packages/@react-aria/table/src/useTableColumnResize.ts @@ -30,11 +30,12 @@ export interface TableColumnResizeAria { export interface AriaTableColumnResizeProps { column: GridNode, label: string, - triggerRef: RefObject + triggerRef: RefObject, + isDisabled?: boolean } export function useTableColumnResize(props: AriaTableColumnResizeProps, state: TableState, columnState: TableColumnResizeState, ref: RefObject): TableColumnResizeAria { - let {column: item, triggerRef} = props; + let {column: item, triggerRef, isDisabled} = props; const stateRef = useRef>(null); // keep track of what the cursor on the body is so it can be restored back to that when done resizing const cursor = useRef(null); @@ -159,7 +160,8 @@ export function useTableColumnResize(props: AriaTableColumnResizeProps, st stateRef.current.onColumnResizeEnd(item); state.setKeyboardNavigationDisabled(false); }, - onChange + onChange, + disabled: isDisabled }, ariaProps ) diff --git a/packages/@react-aria/table/src/useTableSelectionCheckbox.ts b/packages/@react-aria/table/src/useTableSelectionCheckbox.ts index 665c03bc3c7..b4e0a2cb302 100644 --- a/packages/@react-aria/table/src/useTableSelectionCheckbox.ts +++ b/packages/@react-aria/table/src/useTableSelectionCheckbox.ts @@ -64,7 +64,7 @@ export function useTableSelectAllCheckbox(state: TableState): TableSelectA checkboxProps: { 'aria-label': stringFormatter.format(selectionMode === 'single' ? 'select' : 'selectAll'), isSelected: isSelectAll, - isDisabled: selectionMode !== 'multiple', + isDisabled: selectionMode !== 'multiple' || state.collection.size === 0, isIndeterminate: !isEmpty && !isSelectAll, onChange: () => state.selectionManager.toggleSelectAll() } diff --git a/packages/@react-spectrum/card/stories/GridCardView.stories.tsx b/packages/@react-spectrum/card/stories/GridCardView.stories.tsx index 9d3c9176750..77e6bebef05 100644 --- a/packages/@react-spectrum/card/stories/GridCardView.stories.tsx +++ b/packages/@react-spectrum/card/stories/GridCardView.stories.tsx @@ -21,6 +21,7 @@ import {GridLayoutOptions} from '../src/GridLayout'; import {Heading, Text} from '@react-spectrum/text'; import {IllustratedMessage} from '@react-spectrum/illustratedmessage'; import {Image} from '@react-spectrum/image'; +import {Link} from '@react-spectrum/link'; import React, {Key, useMemo, useState} from 'react'; import {Size} from '@react-stately/virtualizer'; import {SpectrumCardViewProps} from '@react-types/card'; @@ -67,7 +68,7 @@ function renderEmptyState() { No results - No results found + No results found, press here for more info. ); } @@ -527,4 +528,3 @@ export function CustomLayout(props: SpectrumCardViewProps & LayoutOption ); } - diff --git a/packages/@react-spectrum/list/stories/ListView.stories.tsx b/packages/@react-spectrum/list/stories/ListView.stories.tsx index c0dc7c8b174..cb21b5b731c 100644 --- a/packages/@react-spectrum/list/stories/ListView.stories.tsx +++ b/packages/@react-spectrum/list/stories/ListView.stories.tsx @@ -20,6 +20,7 @@ import {Image} from '@react-spectrum/image'; import Info from '@spectrum-icons/workflow/Info'; import {Item, ListView} from '../'; import {ItemDropTarget} from '@react-types/shared'; +import {Link} from '@react-spectrum/link'; import NoSearchResults from '@spectrum-icons/illustrations/NoSearchResults'; import React, {useEffect, useState} from 'react'; import {storiesOf} from '@storybook/react'; @@ -106,14 +107,14 @@ const itemsWithThumbs = [ {key: '9', title: 'file of great boi', illustration: } ]; -function renderEmptyState() { +export function renderEmptyState() { return ( No results - No results found + No results found, press here for more info. ); } diff --git a/packages/@react-spectrum/list/test/ListView.test.js b/packages/@react-spectrum/list/test/ListView.test.js index 3875e23c49b..2dd1f4baeb8 100644 --- a/packages/@react-spectrum/list/test/ListView.test.js +++ b/packages/@react-spectrum/list/test/ListView.test.js @@ -17,6 +17,7 @@ import {announce} from '@react-aria/live-announcer'; import {Item, ListView} from '../src'; import {Provider} from '@react-spectrum/provider'; import React from 'react'; +import {renderEmptyState} from '../stories/ListView.stories'; import {Text} from '@react-spectrum/text'; import {theme} from '@react-spectrum/theme-default'; import userEvent from '@testing-library/user-event'; @@ -570,13 +571,20 @@ describe('ListView', function () { }); it('should render empty state', function () { - function renderEmptyState() { - return
No results
; - } let {getByText} = render(); expect(getByText('No results')).toBeTruthy(); }); + it('should allow you to tab into ListView body if empty', function () { + let {getByRole} = render(); + let grid = getByRole('grid'); + let link = getByRole('link'); + userEvent.tab(); + expect(document.activeElement).toBe(grid); + userEvent.tab(); + expect(document.activeElement).toBe(link); + }); + it('supports custom data attributes', () => { let {getByRole} = render( diff --git a/packages/@react-spectrum/table/src/Resizer.tsx b/packages/@react-spectrum/table/src/Resizer.tsx index fe7e33ffe53..910b432bf61 100644 --- a/packages/@react-spectrum/table/src/Resizer.tsx +++ b/packages/@react-spectrum/table/src/Resizer.tsx @@ -19,11 +19,11 @@ interface ResizerProps { function Resizer(props: ResizerProps, ref: RefObject) { let {column, showResizer} = props; - let {state, columnState} = useTableContext(); + let {state, columnState, isEmpty} = useTableContext(); let stringFormatter = useLocalizedStringFormatter(intlMessages); let {direction} = useLocale(); - let {inputProps, resizerProps} = useTableColumnResize({...props, label: stringFormatter.format('columnResizer')}, state, columnState, ref); + let {inputProps, resizerProps} = useTableColumnResize({...props, label: stringFormatter.format('columnResizer'), isDisabled: isEmpty}, state, columnState, ref); let style = { cursor: undefined, diff --git a/packages/@react-spectrum/table/src/TableView.tsx b/packages/@react-spectrum/table/src/TableView.tsx index 3a2483c0d5f..1f5614c5c1d 100644 --- a/packages/@react-spectrum/table/src/TableView.tsx +++ b/packages/@react-spectrum/table/src/TableView.tsx @@ -81,7 +81,8 @@ interface TableContextValue { state: TableState, layout: TableLayout, columnState: TableColumnResizeState, - headerRowHovered: boolean + headerRowHovered: boolean, + isEmpty: boolean } const TableContext = React.createContext>(null); @@ -252,6 +253,7 @@ function TableView(props: SpectrumTableProps, ref: DOMRef; } + // TODO: consider this case, what if we have hidden headers and a empty table if (item.props.hideHeader) { return ( @@ -304,11 +306,13 @@ function TableView(props: SpectrumTableProps, ref: DOMRef + (props: SpectrumTableProps, ref: DOMRef ); } // This is a custom Virtualizer that also has a header that syncs its scroll position with the body. -function TableVirtualizer({layout, collection, focusedKey, renderView, renderWrapper, domRef, bodyRef, setTableWidth, getColumnWidth, onVisibleRectChange: onVisibleRectChangeProp, ...otherProps}) { +function TableVirtualizer({layout, collection, focusedKey, renderView, renderWrapper, domRef, bodyRef, setTableWidth, getColumnWidth, onVisibleRectChange: onVisibleRectChangeProp, isFocusVisible, ...otherProps}) { let {direction} = useLocale(); let headerRef = useRef(); let loadingState = collection.body.props.loadingState; @@ -415,7 +420,8 @@ function TableVirtualizer({layout, collection, focusedKey, renderView, renderWra return (
(null); - let {state} = useTableContext(); + let {state, isEmpty} = useTableContext(); + let {pressProps, isPressed} = usePress({isDisabled: isEmpty}); let {columnHeaderProps} = useTableColumnHeader({ node: column, isVirtualized: true @@ -469,9 +484,9 @@ function TableColumnHeader(props) { let columnProps = column.props as SpectrumColumnProps; - let {hoverProps, isHovered} = useHover(props); + let {hoverProps, isHovered} = useHover({...props, isDisabled: isEmpty}); - const allProps = [columnHeaderProps, hoverProps]; + const allProps = [columnHeaderProps, hoverProps, pressProps]; return ( @@ -483,6 +498,7 @@ function TableColumnHeader(props) { styles, 'spectrum-Table-headCell', { + 'is-active': isPressed, 'is-resizable': columnProps.allowsResizing, 'is-sortable': columnProps.allowsSorting, 'is-sorted-desc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'descending', @@ -513,8 +529,9 @@ function TableColumnHeader(props) { } let _TableColumnHeaderButton = (props, ref: FocusableRef) => { + let {isEmpty} = useTableContext(); let domRef = useFocusableRef(ref); - let {buttonProps} = useButton({...props, elementType: 'div'}, domRef); + let {buttonProps} = useButton({...props, elementType: 'div', isDisabled: isEmpty}, domRef); return (
@@ -530,16 +547,17 @@ function ResizableTableColumnHeader(props) { let ref = useRef(null); let triggerRef = useRef(null); let resizingRef = useRef(null); - let {state, columnState, headerRowHovered} = useTableContext(); + let {state, columnState, headerRowHovered, isEmpty} = useTableContext(); let stringFormatter = useLocalizedStringFormatter(intlMessages); + let {pressProps, isPressed} = usePress({isDisabled: isEmpty}); let {columnHeaderProps} = useTableColumnHeader({ node: column, isVirtualized: true, hasMenu: true }, state, ref); - let {hoverProps, isHovered} = useHover(props); + let {hoverProps, isHovered} = useHover({...props, isDisabled: isEmpty}); - const allProps = [columnHeaderProps, hoverProps]; + const allProps = [columnHeaderProps, hoverProps, pressProps]; let columnProps = column.props as SpectrumColumnProps; @@ -589,7 +607,7 @@ function ResizableTableColumnHeader(props) { } }, [columnState.currentlyResizingColumn, column.key]); - let showResizer = headerRowHovered || columnState.currentlyResizingColumn != null; + let showResizer = !isEmpty && (headerRowHovered || columnState.currentlyResizingColumn != null); return ( @@ -601,6 +619,7 @@ function ResizableTableColumnHeader(props) { styles, 'spectrum-Table-headCell', { + 'is-active': isPressed, 'is-resizable': columnProps.allowsResizing, 'is-sortable': columnProps.allowsSorting, 'is-sorted-desc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'descending', @@ -685,7 +704,6 @@ function TableSelectAllCell({column}) { } diff --git a/packages/@react-spectrum/table/stories/Table.stories.tsx b/packages/@react-spectrum/table/stories/Table.stories.tsx index 6aca4d953c0..9fa5fbdfae8 100644 --- a/packages/@react-spectrum/table/stories/Table.stories.tsx +++ b/packages/@react-spectrum/table/stories/Table.stories.tsx @@ -97,7 +97,7 @@ function renderEmptyState() { No results - No results found + No results found, press here for more info. ); } @@ -936,16 +936,7 @@ storiesOf('TableView', module) .add( 'renderEmptyState', () => ( - - - {column => - {column.name} - } - - - {[]} - - + ) ) .add( @@ -1661,7 +1652,6 @@ export function TableWithBreadcrumbs() { const [loadingState, setLoadingState] = useState('idle' as 'idle'); const [selection, setSelection] = useState<'all' | Iterable>(new Set([])); const [items, setItems] = useState(() => fs.filter(item => !item.parent)); - const changeFolder = (folder) => { setItems([]); setLoadingState('loading' as 'loading'); @@ -1720,6 +1710,29 @@ export function TableWithBreadcrumbs() { ); } +export function EmptyStateTable() { + let [show, setShow] = useState(false); + let [sortDescriptor, setSortDescriptor] = useState({}); + return ( + + setShow(show => !show)}>Toggle items + + + {column => + {column.name} + } + + + {item => + ( + {key => {item[key]}} + ) + } + + + + ); +} function EmptyState() { return ( @@ -1761,4 +1774,3 @@ function ZoomResizing() {
); } - diff --git a/packages/@react-spectrum/table/test/Table.test.js b/packages/@react-spectrum/table/test/Table.test.js index 39cbe5dc6eb..4a5918c9630 100644 --- a/packages/@react-spectrum/table/test/Table.test.js +++ b/packages/@react-spectrum/table/test/Table.test.js @@ -21,13 +21,13 @@ import {Content} from '@react-spectrum/view'; import {CRUDExample} from '../stories/CRUDExample'; import {Dialog, DialogTrigger} from '@react-spectrum/dialog'; import {Divider} from '@react-spectrum/divider'; +import {EmptyStateTable, TableWithBreadcrumbs} from '../stories/Table.stories'; import {getFocusableTreeWalker} from '@react-aria/focus'; import {Heading} from '@react-spectrum/text'; import {Link} from '@react-spectrum/link'; import {Provider} from '@react-spectrum/provider'; import React from 'react'; import {Switch} from '@react-spectrum/switch'; -import {TableWithBreadcrumbs} from '../stories/Table.stories'; import {TextField} from '@react-spectrum/textfield'; import {theme} from '@react-spectrum/theme-default'; import userEvent from '@testing-library/user-event'; @@ -93,6 +93,25 @@ function ExampleSortTable() { ); } +let defaultTable = ( + + + Foo + Bar + + + + Foo 1 + Bar 1 + + + Foo 2 + Bar 2 + + + +); + function pointerEvent(type, opts) { let evt = new Event(type, {bubbles: true, cancelable: true}); Object.assign(evt, { @@ -3682,25 +3701,6 @@ describe('TableView', function () { }); describe('async loading', function () { - let defaultTable = ( - - - Foo - Bar - - - - Foo 1 - Bar 1 - - - Foo 2 - Bar 2 - - - - ); - it('should display a spinner when loading', function () { let tree = render( @@ -3871,75 +3871,6 @@ describe('TableView', function () { // this is a good candidate for storybook interactions test expect(onLoadMoreSpy).toHaveBeenCalledTimes(4); }); - - it('should display an empty state when there are no items', function () { - let tree = render( -

No results

}> - - Foo - Bar - - - {[]} - -
- ); - - let table = tree.getByRole('grid'); - let rows = within(table).getAllByRole('row'); - expect(rows).toHaveLength(2); - expect(rows[1]).toHaveAttribute('aria-rowindex', '2'); - - let cell = within(rows[1]).getByRole('rowheader'); - expect(cell).toHaveAttribute('aria-colspan', '2'); - - let heading = within(cell).getByRole('heading'); - expect(heading).toBeVisible(); - expect(heading).toHaveTextContent('No results'); - - rerender(tree, defaultTable); - act(() => jest.runAllTimers()); - - rows = within(table).getAllByRole('row'); - expect(rows).toHaveLength(3); - expect(heading).not.toBeInTheDocument(); - }); - - it('empty table select all should do nothing', function () { - let onSelectionChange = jest.fn(); - let tree = render( -
-

No results

}> - - Foo - Bar - - - {[]} - -
- -
- ); - - let table = tree.getByRole('grid'); - let selectAll = tree.getByRole('checkbox'); - let rows = within(table).getAllByRole('row'); - expect(rows).toHaveLength(2); - expect(rows[1]).toHaveAttribute('aria-rowindex', '2'); - - userEvent.tab(); - expect(document.activeElement).toBe(table); - userEvent.tab(); - // shift tab with userEvent doesn't bring you to the right place - fireEvent.keyDown(document.activeElement, {key: 'Tab', shift: true}); - fireEvent.keyUp(document.activeElement, {key: 'Tab', shift: true}); - expect(document.activeElement).toBe(selectAll); - - userEvent.type(document.activeElement, '{space}'); - expect(onSelectionChange).toHaveBeenCalledWith('all'); - expect(selectAll).toHaveAttribute('aria-checked', 'true'); - }); }); describe('sorting', function () { @@ -4206,4 +4137,158 @@ describe('TableView', function () { expect(onSortChange).toHaveBeenCalledWith({column: 'bar', direction: 'ascending'}); }); }); + + describe('empty state', function () { + it('should display an empty state when there are no items', function () { + let tree = render( +

No results

}> + + Foo + Bar + + + {[]} + +
+ ); + + let table = tree.getByRole('grid'); + let rows = within(table).getAllByRole('row'); + expect(rows).toHaveLength(2); + expect(rows[1]).toHaveAttribute('aria-rowindex', '2'); + + let cell = within(rows[1]).getByRole('rowheader'); + expect(cell).toHaveAttribute('aria-colspan', '2'); + + let heading = within(cell).getByRole('heading'); + expect(heading).toBeVisible(); + expect(heading).toHaveTextContent('No results'); + + rerender(tree, defaultTable); + act(() => jest.runAllTimers()); + + rows = within(table).getAllByRole('row'); + expect(rows).toHaveLength(3); + expect(heading).not.toBeInTheDocument(); + }); + + it('empty table select all should be disabled', function () { + let onSelectionChange = jest.fn(); + let tree = render( +
+

No results

}> + + Foo + Bar + + + {[]} + +
+ +
+ ); + + let table = tree.getByRole('grid'); + let selectAll = tree.getByRole('checkbox'); + + userEvent.tab(); + expect(document.activeElement).toBe(table); + userEvent.tab(); + expect(document.activeElement).not.toBe(selectAll); + expect(selectAll).toHaveAttribute('disabled'); + }); + + it('should allow the user to tab into the table body', function () { + let tree = render(); + let table = tree.getByRole('grid'); + let toggleButton = tree.getAllByRole('button')[0]; + let link = tree.getByRole('link'); + + userEvent.tab(); + expect(document.activeElement).toBe(toggleButton); + userEvent.tab(); + expect(document.activeElement).toBe(table); + userEvent.tab(); + expect(document.activeElement).toBe(link); + }); + + it('should disable keyboard navigation within the table', function () { + let tree = render(); + let table = tree.getByRole('grid'); + let header = within(table).getAllByRole('columnheader')[2]; + expect(header).not.toHaveAttribute('tabindex'); + let headerButton = within(header).getByRole('button'); + expect(headerButton).toHaveAttribute('aria-disabled', 'true'); + // Can't progamatically focus the column headers since they have no tab index when table is empty + act(() => { + header.focus(); + }); + expect(document.activeElement).toBe(document.body); + }); + + it('should disable press interactions with the column headers', function () { + let tree = render(); + let table = tree.getByRole('grid'); + let headers = within(table).getAllByRole('columnheader'); + let toggleButton = tree.getAllByRole('button')[0]; + + userEvent.tab(); + expect(document.activeElement).toBe(toggleButton); + + let columnButton = within(headers[2]).getByRole('button'); + triggerPress(columnButton); + expect(document.activeElement).toBe(toggleButton); + expect(tree.queryByRole('menuitem')).toBeFalsy(); + fireEvent.mouseEnter(headers[2]); + act(() => {jest.runAllTimers();}); + expect(tree.queryByRole('slider')).toBeFalsy(); + }); + + it.skip('should re-enable functionality when the table recieves items', function () { + let tree = render(); + let table = tree.getByRole('grid'); + let headers = within(table).getAllByRole('columnheader'); + let toggleButton = tree.getAllByRole('button')[0]; + let selectAll = tree.getByRole('checkbox'); + + userEvent.tab(); + expect(document.activeElement).toBe(toggleButton); + triggerPress(toggleButton); + act(() => {jest.runAllTimers();}); + + expect(selectAll).not.toHaveAttribute('disabled'); + triggerPress(selectAll); + act(() => {jest.runAllTimers();}); + expect(selectAll.checked).toBeTruthy(); + expect(document.activeElement).toBe(selectAll); + + fireEvent.mouseEnter(headers[2]); + act(() => {jest.runAllTimers();}); + expect(tree.queryAllByRole('slider')).toBeTruthy(); + + let column1Button = within(headers[1]).getByRole('button'); + let column2Button = within(headers[2]).getByRole('button'); + triggerPress(column2Button); + act(() => {jest.runAllTimers();}); + expect(tree.queryAllByRole('menuitem')).toBeTruthy(); + fireEvent.keyDown(document.activeElement, {key: 'Escape'}); + fireEvent.keyUp(document.activeElement, {key: 'Escape'}); + act(() => {jest.runAllTimers();}); + act(() => {jest.runAllTimers();}); + expect(document.activeElement).toBe(column2Button); + fireEvent.keyDown(document.activeElement, {key: 'ArrowLeft', code: 37, charCode: 37}); + fireEvent.keyUp(document.activeElement, {key: 'ArrowLeft', code: 37, charCode: 37}); + expect(document.activeElement).toBe(column1Button); + + triggerPress(toggleButton); + act(() => {jest.runAllTimers();}); + expect(selectAll).toHaveAttribute('disabled'); + triggerPress(headers[2]); + expect(document.activeElement).toBe(toggleButton); + userEvent.tab(); + expect(document.activeElement).toBe(table); + expect(table).toHaveAttribute('tabIndex', '0'); + }); + }); }); diff --git a/packages/@react-stately/table/src/useTableState.ts b/packages/@react-stately/table/src/useTableState.ts index f88d3c16a25..4466db543ee 100644 --- a/packages/@react-stately/table/src/useTableState.ts +++ b/packages/@react-stately/table/src/useTableState.ts @@ -76,7 +76,7 @@ export function useTableState(props: TableStateProps): Tabl selectionManager, showSelectionCheckboxes: props.showSelectionCheckboxes || false, sortDescriptor: props.sortDescriptor, - isKeyboardNavigationDisabled, + isKeyboardNavigationDisabled: collection.size === 0 || isKeyboardNavigationDisabled, setKeyboardNavigationDisabled, sort(columnKey: Key, direction?: 'ascending' | 'descending') { props.onSortChange({