Skip to content

Commit

Permalink
Always return focus to the toggle whenever the list view is closed, r…
Browse files Browse the repository at this point in the history
…oll out to widgets editor
  • Loading branch information
andrewserong committed Sep 20, 2023
1 parent 53f0a7f commit 5175627
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
/**
* WordPress dependencies
*/
import {
__experimentalListView as ListView,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { __experimentalListView as ListView } from '@wordpress/block-editor';
import { Button, TabPanel } from '@wordpress/components';
import {
useFocusOnMount,
useFocusReturn,
useMergeRefs,
} from '@wordpress/compose';
import { useSelect, useDispatch } from '@wordpress/data';
import { useFocusOnMount, useMergeRefs } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
import { focus } from '@wordpress/dom';
import { useCallback, useRef, useState } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
Expand All @@ -26,30 +19,25 @@ import { store as editPostStore } from '../../store';
import ListViewOutline from './list-view-outline';

export default function ListViewSidebar( { listViewToggleElement } ) {
const hasBlocksSelected = useSelect(
( select ) => !! select( blockEditorStore ).getBlockSelectionStart(),
[]
);
const { setIsListViewOpened } = useDispatch( editPostStore );

// This hook handles focus when the sidebar first renders.
const focusOnMountRef = useFocusOnMount( 'firstElement' );
// The next 2 hooks handle focus for when the sidebar closes and returning focus to the element that had focus before sidebar opened.
const headerFocusReturnRef = useFocusReturn();
const contentFocusReturnRef = useFocusReturn();

// When closing the list view, focus should return to the toggle button.
const closeListView = useCallback( () => {
setIsListViewOpened( false );
listViewToggleElement?.focus();
}, [ listViewToggleElement, setIsListViewOpened ] );

const closeOnEscape = useCallback(
( event ) => {
if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) {
event.preventDefault();
setIsListViewOpened( false );

if ( ! hasBlocksSelected ) {
listViewToggleElement?.focus();
}
closeListView();
}
},
[ hasBlocksSelected, listViewToggleElement, setIsListViewOpened ]
[ closeListView ]
);

// Use internal state instead of a ref to make sure that the component
Expand All @@ -67,7 +55,6 @@ export default function ListViewSidebar( { listViewToggleElement } ) {

// Must merge the refs together so focus can be handled properly in the next function.
const listViewContainerRef = useMergeRefs( [
contentFocusReturnRef,
focusOnMountRef,
listViewRef,
setDropZoneElement,
Expand Down Expand Up @@ -108,17 +95,12 @@ export default function ListViewSidebar( { listViewToggleElement } ) {
sidebarRef.current.ownerDocument.activeElement
)
) {
setIsListViewOpened( false );
// When no block is selected and the sidebar is closed,
// focus should be returned to the list view toggle button.
if ( ! hasBlocksSelected ) {
listViewToggleElement?.focus();
}
closeListView();
} else {
// If the list view or outline does not have focus, focus should be moved to it.
handleSidebarFocus( tab );
}
}, [ hasBlocksSelected, listViewToggleElement, setIsListViewOpened, tab ] );
}, [ closeListView, tab ] );

// This only fires when the sidebar is open because of the conditional rendering.
// It is the same shortcut to open but that is defined as a global shortcut and only fires when the sidebar is closed.
Expand Down Expand Up @@ -152,10 +134,9 @@ export default function ListViewSidebar( { listViewToggleElement } ) {
>
<Button
className="edit-post-editor__document-overview-panel__close-button"
ref={ headerFocusReturnRef }
icon={ closeSmall }
label={ __( 'Close' ) }
onClick={ () => setIsListViewOpened( false ) }
onClick={ closeListView }
/>
<TabPanel
className="edit-post-editor__document-overview-panel__tab-panel"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
/**
* WordPress dependencies
*/
import {
privateApis as blockEditorPrivateApis,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import {
useFocusOnMount,
useFocusReturn,
useMergeRefs,
} from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
import { useFocusOnMount, useMergeRefs } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
import { useCallback, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { closeSmall } from '@wordpress/icons';
Expand All @@ -28,30 +21,25 @@ import { unlock } from '../../lock-unlock';
const { PrivateListView } = unlock( blockEditorPrivateApis );

export default function ListViewSidebar( { listViewToggleElement } ) {
const hasBlocksSelected = useSelect(
( select ) => !! select( blockEditorStore ).getBlockSelectionStart(),
[]
);
const { setIsListViewOpened } = useDispatch( editSiteStore );

// This hook handles focus when the sidebar first renders.
const focusOnMountRef = useFocusOnMount( 'firstElement' );
// The next 2 hooks handle focus for when the sidebar closes and returning focus to the element that had focus before sidebar opened.
const headerFocusReturnRef = useFocusReturn();
const contentFocusReturnRef = useFocusReturn();

// When closing the list view, focus should return to the toggle button.
const closeListView = useCallback( () => {
setIsListViewOpened( false );
listViewToggleElement?.focus();
}, [ listViewToggleElement, setIsListViewOpened ] );

const closeOnEscape = useCallback(
( event ) => {
if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) {
event.preventDefault();
setIsListViewOpened( false );

if ( ! hasBlocksSelected ) {
listViewToggleElement?.focus();
}
closeListView();
}
},
[ hasBlocksSelected, listViewToggleElement, setIsListViewOpened ]
[ closeListView ]
);

// Use internal state instead of a ref to make sure that the component
Expand Down Expand Up @@ -90,17 +78,12 @@ export default function ListViewSidebar( { listViewToggleElement } ) {
sidebarRef.current.ownerDocument.activeElement
)
) {
setIsListViewOpened( false );
// When no block is selected and the sidebar is closed,
// focus should be returned to the list view toggle button.
if ( ! hasBlocksSelected ) {
listViewToggleElement?.focus();
}
closeListView();
} else {
// If the list view or close button does not have focus, focus should be moved to it.
handleSidebarFocus();
}
}, [ hasBlocksSelected, listViewToggleElement, setIsListViewOpened ] );
}, [ closeListView ] );

// This only fires when the sidebar is open because of the conditional rendering.
// It is the same shortcut to open but that is defined as a global shortcut and only fires when the sidebar is closed.
Expand All @@ -116,22 +99,18 @@ export default function ListViewSidebar( { listViewToggleElement } ) {
onKeyDown={ closeOnEscape }
ref={ sidebarRef }
>
<div
className="edit-site-editor__list-view-panel-header"
ref={ headerFocusReturnRef }
>
<div className="edit-site-editor__list-view-panel-header">
<strong>{ __( 'List View' ) }</strong>
<Button
icon={ closeSmall }
label={ __( 'Close' ) }
onClick={ () => setIsListViewOpened( false ) }
onClick={ closeListView }
ref={ sidebarCloseButtonRef }
/>
</div>
<div
className="edit-site-editor__list-view-panel-content"
ref={ useMergeRefs( [
contentFocusReturnRef,
focusOnMountRef,
setDropZoneElement,
listViewRef,
Expand Down
3 changes: 2 additions & 1 deletion packages/edit-widgets/src/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { unlock } from '../../lock-unlock';

const { useShouldContextualToolbarShow } = unlock( blockEditorPrivateApis );

function Header() {
function Header( { setListViewToggleElement } ) {
const isMediumViewport = useViewportMatch( 'medium' );
const inserterButton = useRef();
const widgetAreaClientId = useLastSelectedWidgetArea();
Expand Down Expand Up @@ -140,6 +140,7 @@ function Header() {
/* translators: button label text should, if possible, be under 16 characters. */
label={ __( 'List View' ) }
onClick={ toggleListView }
ref={ setListViewToggleElement }
/>
</>
) }
Expand Down
17 changes: 14 additions & 3 deletions packages/edit-widgets/src/components/layout/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { useViewportMatch } from '@wordpress/compose';
import { BlockBreadcrumb } from '@wordpress/block-editor';
import { useEffect } from '@wordpress/element';
import { useEffect, useState } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import {
InterfaceSkeleton,
Expand Down Expand Up @@ -68,6 +68,9 @@ function Interface( { blockEditorSettings } ) {
[]
);

const [ listViewToggleElement, setListViewToggleElement ] =
useState( null );

// Inserter and Sidebars are mutually exclusive
useEffect( () => {
if ( hasSidebarEnabled && ! isHugeViewport ) {
Expand All @@ -94,8 +97,16 @@ function Interface( { blockEditorSettings } ) {
...interfaceLabels,
secondarySidebar: secondarySidebarLabel,
} }
header={ <Header /> }
secondarySidebar={ hasSecondarySidebar && <SecondarySidebar /> }
header={
<Header setListViewToggleElement={ setListViewToggleElement } />
}
secondarySidebar={
hasSecondarySidebar && (
<SecondarySidebar
listViewToggleElement={ listViewToggleElement }
/>
)
}
sidebar={
hasSidebarEnabled && (
<ComplementaryArea.Slot scope="core/edit-widgets" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { store as editWidgetsStore } from '../../store';
import InserterSidebar from './inserter-sidebar';
import ListViewSidebar from './list-view-sidebar';

export default function SecondarySidebar() {
export default function SecondarySidebar( { listViewToggleElement } ) {
const { isInserterOpen, isListViewOpen } = useSelect( ( select ) => {
const { isInserterOpened, isListViewOpened } =
select( editWidgetsStore );
Expand All @@ -27,7 +27,9 @@ export default function SecondarySidebar() {
return <InserterSidebar />;
}
if ( isListViewOpen ) {
return <ListViewSidebar />;
return (
<ListViewSidebar listViewToggleElement={ listViewToggleElement } />
);
}
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
*/
import { __experimentalListView as ListView } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import {
useFocusOnMount,
useFocusReturn,
useMergeRefs,
} from '@wordpress/compose';
import { useFocusOnMount, useMergeRefs } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { useCallback, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { closeSmall } from '@wordpress/icons';
import { ESCAPE } from '@wordpress/keycodes';
Expand All @@ -19,48 +15,48 @@ import { ESCAPE } from '@wordpress/keycodes';
*/
import { store as editWidgetsStore } from '../../store';

export default function ListViewSidebar() {
export default function ListViewSidebar( { listViewToggleElement } ) {
const { setIsListViewOpened } = useDispatch( editWidgetsStore );

// Use internal state instead of a ref to make sure that the component
// re-renders when the dropZoneElement updates.
const [ dropZoneElement, setDropZoneElement ] = useState( null );

const focusOnMountRef = useFocusOnMount( 'firstElement' );
const headerFocusReturnRef = useFocusReturn();
const contentFocusReturnRef = useFocusReturn();

function closeOnEscape( event ) {
if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) {
event.preventDefault();
setIsListViewOpened( false );
}
}
// When closing the list view, focus should return to the toggle button.
const closeListView = useCallback( () => {
setIsListViewOpened( false );
listViewToggleElement?.focus();
}, [ listViewToggleElement, setIsListViewOpened ] );

const closeOnEscape = useCallback(
( event ) => {
if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) {
event.preventDefault();
closeListView();
}
},
[ closeListView ]
);

return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className="edit-widgets-editor__list-view-panel"
onKeyDown={ closeOnEscape }
>
<div
className="edit-widgets-editor__list-view-panel-header"
ref={ headerFocusReturnRef }
>
<div className="edit-widgets-editor__list-view-panel-header">
<strong>{ __( 'List View' ) }</strong>
<Button
icon={ closeSmall }
label={ __( 'Close' ) }
onClick={ () => setIsListViewOpened( false ) }
onClick={ closeListView }
/>
</div>
<div
className="edit-widgets-editor__list-view-panel-content"
ref={ useMergeRefs( [
contentFocusReturnRef,
focusOnMountRef,
setDropZoneElement,
] ) }
ref={ useMergeRefs( [ focusOnMountRef, setDropZoneElement ] ) }
>
<ListView dropZoneElement={ dropZoneElement } />
</div>
Expand Down

0 comments on commit 5175627

Please sign in to comment.