diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js
index 5bfcba5def4aa3..083931bb5203ec 100644
--- a/packages/dataviews/src/view-table.js
+++ b/packages/dataviews/src/view-table.js
@@ -1,3 +1,8 @@
+/**
+ * External dependencies
+ */
+import classNames from 'classnames';
+
/**
* WordPress dependencies
*/
@@ -9,7 +14,15 @@ import {
Icon,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
-import { Children, Fragment } from '@wordpress/element';
+import {
+ Children,
+ Fragment,
+ forwardRef,
+ useEffect,
+ useId,
+ useRef,
+ useState,
+} from '@wordpress/element';
/**
* Internal dependencies
@@ -41,7 +54,10 @@ const sanitizeOperators = ( field ) => {
);
};
-function HeaderMenu( { field, view, onChangeView } ) {
+const HeaderMenu = forwardRef( function HeaderMenu(
+ { field, view, onChangeView, onHide },
+ ref
+) {
const isHidable = field.enableHiding !== false;
const isSortable = field.enableSorting !== false;
@@ -76,6 +92,7 @@ function HeaderMenu( { field, view, onChangeView } ) {
size="compact"
className="dataviews-table-header-button"
style={ { padding: 0 } }
+ ref={ ref }
>
{ field.header }
{ isSorted && (
@@ -132,6 +149,7 @@ function HeaderMenu( { field, view, onChangeView } ) {
}
onClick={ () => {
+ onHide( field );
onChangeView( {
...view,
hiddenFields: view.hiddenFields.concat(
@@ -275,7 +293,7 @@ function HeaderMenu( { field, view, onChangeView } ) {
);
-}
+} );
function WithSeparators( { children } ) {
return Children.toArray( children )
@@ -298,6 +316,35 @@ function ViewTable( {
isLoading = false,
deferredRendering,
} ) {
+ const headerMenuRefs = useRef( new Map() );
+ const headerMenuToFocusRef = useRef();
+ const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] = useState();
+
+ useEffect( () => {
+ if ( headerMenuToFocusRef.current ) {
+ headerMenuToFocusRef.current.focus();
+ headerMenuToFocusRef.current = undefined;
+ }
+ } );
+
+ const asyncData = useAsyncList( data );
+ const tableNoticeId = useId();
+
+ if ( nextHeaderMenuToFocus ) {
+ // If we need to force focus, we short-circuit rendering here
+ // to prevent any additional work while we handle that.
+ // Clearing out the focus directive is necessary to make sure
+ // future renders don't cause unexpected focus jumps.
+ headerMenuToFocusRef.current = nextHeaderMenuToFocus;
+ setNextHeaderMenuToFocus();
+ return;
+ }
+
+ const onHide = ( field ) => {
+ const hidden = headerMenuRefs.current.get( field.id );
+ const fallback = headerMenuRefs.current.get( hidden.fallback );
+ setNextHeaderMenuToFocus( fallback?.node );
+ };
const visibleFields = fields.filter(
( field ) =>
! view.hiddenFields.includes( field.id ) &&
@@ -305,55 +352,70 @@ function ViewTable( {
field.id
)
);
- const shownData = useAsyncList( data );
- const usedData = deferredRendering ? shownData : data;
+ const usedData = deferredRendering ? asyncData : data;
const hasData = !! usedData?.length;
- if ( isLoading ) {
- // TODO:Add spinner or progress bar..
- return (
-
-
{ __( 'Loading' ) }
-
- );
- }
const sortValues = { asc: 'ascending', desc: 'descending' };
+
return (
- { hasData && (
-
-
-
- { visibleFields.map( ( field ) => (
-
+
+
+ { visibleFields.map( ( field, index ) => (
+
+ {
+ if ( node ) {
+ headerMenuRefs.current.set(
+ field.id,
+ {
+ node,
+ fallback:
+ visibleFields[
+ index > 0
+ ? index - 1
+ : 1
+ ]?.id,
+ }
+ );
+ } else {
+ headerMenuRefs.current.delete(
+ field.id
+ );
+ }
} }
- data-field-id={ field.id }
- aria-sort={
- view.sort?.field === field.id &&
- sortValues[ view.sort.direction ]
- }
- scope="col"
- >
-
- |
- ) ) }
- { !! actions?.length && (
-
- { __( 'Actions' ) }
- |
- ) }
-
-
- |
- { usedData.map( ( item ) => (
+ field={ field }
+ view={ view }
+ onChangeView={ onChangeView }
+ onHide={ onHide }
+ />
+
+ ) ) }
+ { !! actions?.length && (
+ { __( 'Actions' ) } |
+ ) }
+
+
+
+ { hasData &&
+ usedData.map( ( item ) => (
{ visibleFields.map( ( field ) => (
) ) }
- |
-
- ) }
- { ! hasData && (
-
-
{ __( 'No results' ) }
-
- ) }
+
+
+
+ { ! hasData && (
+
{ isLoading ? __( 'Loading…' ) : __( 'No results' ) }
+ ) }
+
);
}