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 ) => ( + - ) ) } - { !! actions?.length && ( - - ) } - - - - { usedData.map( ( item ) => ( + field={ field } + view={ view } + onChangeView={ onChangeView } + onHide={ onHide } + /> + + ) ) } + { !! actions?.length && ( + + ) } + + + + { hasData && + usedData.map( ( item ) => ( { visibleFields.map( ( field ) => ( -
+
+ { + 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' ) } -
{ __( 'Actions' ) }
) ) } -
- ) } - { ! hasData && ( -
-

{ __( 'No results' ) }

-
- ) } + + +
+ { ! hasData && ( +

{ isLoading ? __( 'Loading…' ) : __( 'No results' ) }

+ ) } +
); }