Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Always return focus to the toggle whenever the list view is closed, r…
…oll out to widgets editor
  • Loading branch information
andrewserong committed Sep 20, 2023
commit 5175627191c061090bc3c2b9d3b120203c22604b
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