Skip to content

Commit

Permalink
Focus inserter toggle when closing the inserter sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
jeryj committed May 8, 2024
1 parent 4c28ed3 commit ddaea17
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 79 deletions.
80 changes: 30 additions & 50 deletions packages/edit-widgets/src/components/header/document-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,70 @@
import { useSelect, useDispatch } from '@wordpress/data';
import { __, _x } from '@wordpress/i18n';
import { Button, ToolbarItem } from '@wordpress/components';
import {
NavigableToolbar,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { NavigableToolbar } from '@wordpress/block-editor';
import { listView, plus } from '@wordpress/icons';
import { useCallback, useRef } from '@wordpress/element';
import { useCallback } from '@wordpress/element';
import { useViewportMatch } from '@wordpress/compose';

/**
* Internal dependencies
*/
import UndoButton from '../undo-redo/undo';
import RedoButton from '../undo-redo/redo';
import useLastSelectedWidgetArea from '../../../hooks/use-last-selected-widget-area';
import { store as editWidgetsStore } from '../../../store';
import { unlock } from '../../../lock-unlock';

function DocumentTools() {
const isMediumViewport = useViewportMatch( 'medium' );
const inserterButton = useRef();
const widgetAreaClientId = useLastSelectedWidgetArea();
const isLastSelectedWidgetAreaOpen = useSelect(
( select ) =>
select( editWidgetsStore ).getIsWidgetAreaOpen(
widgetAreaClientId
),
[ widgetAreaClientId ]
);
const { isInserterOpen, isListViewOpen, listViewToggleRef } = useSelect(
( select ) => {
const { isInserterOpened, isListViewOpened, getListViewToggleRef } =
unlock( select( editWidgetsStore ) );
return {
isInserterOpen: isInserterOpened(),
isListViewOpen: isListViewOpened(),
listViewToggleRef: getListViewToggleRef(),
};
},
[]
);
const { setIsWidgetAreaOpen, setIsInserterOpened, setIsListViewOpened } =

const {
isInserterOpen,
isListViewOpen,
inserterSidebarToggleRef,
listViewToggleRef,
} = useSelect( ( select ) => {
const {
isInserterOpened,
getInserterSidebarToggleRef,
isListViewOpened,
getListViewToggleRef,
} = unlock( select( editWidgetsStore ) );
return {
isInserterOpen: isInserterOpened(),
isListViewOpen: isListViewOpened(),
inserterSidebarToggleRef: getInserterSidebarToggleRef(),
listViewToggleRef: getListViewToggleRef(),
};
}, [] );
const { setIsInserterOpened, setIsListViewOpened } =
useDispatch( editWidgetsStore );
const { selectBlock } = useDispatch( blockEditorStore );
const handleClick = () => {
if ( isInserterOpen ) {
// Focusing the inserter button closes the inserter popover.
setIsInserterOpened( false );
} else {
if ( ! isLastSelectedWidgetAreaOpen ) {
// Select the last selected block if hasn't already.
selectBlock( widgetAreaClientId );
// Open the last selected widget area when opening the inserter.
setIsWidgetAreaOpen( widgetAreaClientId, true );
}
// The DOM updates resulting from selectBlock() and setIsInserterOpened() calls are applied the
// same tick and pretty much in a random order. The inserter is closed if any other part of the
// app receives focus. If selectBlock() happens to take effect after setIsInserterOpened() then
// the inserter is visible for a brief moment and then gets auto-closed due to focus moving to
// the selected block.
window.requestAnimationFrame( () => setIsInserterOpened( true ) );
}
};

const toggleListView = useCallback(
() => setIsListViewOpened( ! isListViewOpen ),
[ setIsListViewOpened, isListViewOpen ]
);

const toggleInserterSidebar = useCallback(
() => setIsInserterOpened( ! isInserterOpen ),
[ setIsInserterOpened, isInserterOpen ]
);

return (
<NavigableToolbar
className="edit-widgets-header-toolbar"
aria-label={ __( 'Document tools' ) }
variant="unstyled"
>
<ToolbarItem
ref={ inserterButton }
ref={ inserterSidebarToggleRef }
as={ Button }
className="edit-widgets-header-toolbar__inserter-toggle"
variant="primary"
isPressed={ isInserterOpen }
onMouseDown={ ( event ) => {
event.preventDefault();
} }
onClick={ handleClick }
onClick={ toggleInserterSidebar }
icon={ plus }
/* translators: button label text should, if possible, be under 16
characters. */
Expand Down
13 changes: 13 additions & 0 deletions packages/edit-widgets/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,21 @@ export function listViewToggleRef( state = { current: null } ) {
return state;
}

/**
* This reducer does nothing aside initializing a ref to the inserter sidebar toggle.
* We will have a unique ref per "editor" instance.
*
* @param {Object} state
* @return {Object} Reference to the inserter sidebar toggle button.
*/
export function inserterSidebarToggleRef( state = { current: null } ) {
return state;
}

export default combineReducers( {
blockInserterPanel,
inserterSidebarToggleRef,
listViewPanel,
listViewToggleRef,
widgetAreasOpenState,
} );
30 changes: 13 additions & 17 deletions packages/editor/src/components/document-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@wordpress/block-editor';
import { Button, ToolbarItem } from '@wordpress/components';
import { listView, plus } from '@wordpress/icons';
import { useRef, useCallback } from '@wordpress/element';
import { useCallback } from '@wordpress/element';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { store as preferencesStore } from '@wordpress/preferences';

Expand All @@ -39,23 +39,25 @@ function DocumentTools( {
// This is a temporary prop until the list view is fully unified between post and site editors.
listViewLabel = __( 'Document Overview' ),
} ) {
const inserterButton = useRef();
const { setIsInserterOpened, setIsListViewOpened } =
useDispatch( editorStore );
const {
isDistractionFree,
isInserterOpened,
isListViewOpen,
listViewShortcut,
inserterSidebarToggleRef,
listViewToggleRef,
hasFixedToolbar,
showIconLabels,
} = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
const { get } = select( preferencesStore );
const { isListViewOpened, getListViewToggleRef } = unlock(
select( editorStore )
);
const {
isListViewOpened,
getInserterSidebarToggleRef,
getListViewToggleRef,
} = unlock( select( editorStore ) );
const { getShortcutRepresentation } = select( keyboardShortcutsStore );

return {
Expand All @@ -64,6 +66,7 @@ function DocumentTools( {
listViewShortcut: getShortcutRepresentation(
'core/editor/toggle-list-view'
),
inserterSidebarToggleRef: getInserterSidebarToggleRef(),
listViewToggleRef: getListViewToggleRef(),
hasFixedToolbar: getSettings().hasFixedToolbar,
showIconLabels: get( 'core', 'showIconLabels' ),
Expand All @@ -82,17 +85,10 @@ function DocumentTools( {
[ setIsListViewOpened, isListViewOpen ]
);

const toggleInserter = useCallback( () => {
if ( isInserterOpened ) {
// Focusing the inserter button should close the inserter popover.
// However, there are some cases it won't close when the focus is lost.
// See https://github.com/WordPress/gutenberg/issues/43090 for more details.
inserterButton.current.focus();
setIsInserterOpened( false );
} else {
setIsInserterOpened( true );
}
}, [ isInserterOpened, setIsInserterOpened ] );
const toggleInserter = useCallback(
() => setIsInserterOpened( ! isInserterOpened ),
[ isInserterOpened, setIsInserterOpened ]
);

/* translators: button label text should, if possible, be under 16 characters. */
const longLabel = _x(
Expand All @@ -118,7 +114,7 @@ function DocumentTools( {
<div className="editor-document-tools__left">
{ ! isDistractionFree && (
<ToolbarItem
ref={ inserterButton }
ref={ inserterSidebarToggleRef }
as={ Button }
className="editor-document-tools__inserter-toggle"
variant="primary"
Expand Down
45 changes: 33 additions & 12 deletions packages/editor/src/components/inserter-sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { __experimentalLibrary as Library } from '@wordpress/block-editor';
import { closeSmall } from '@wordpress/icons';
import { useViewportMatch } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { useRef } from '@wordpress/element';
import { useCallback, useRef } from '@wordpress/element';
import { store as preferencesStore } from '@wordpress/preferences';

import { ESCAPE } from '@wordpress/keycodes';
/**
* Internal dependencies
*/
Expand All @@ -20,27 +20,48 @@ export default function InserterSidebar( {
closeGeneralSidebar,
isRightSidebarOpen,
} ) {
const { insertionPoint, showMostUsedBlocks } = useSelect( ( select ) => {
const { getInsertionPoint } = unlock( select( editorStore ) );
const { get } = select( preferencesStore );
return {
insertionPoint: getInsertionPoint(),
showMostUsedBlocks: get( 'core', 'mostUsedBlocks' ),
};
}, [] );
const { inserterSidebarToggleRef, insertionPoint, showMostUsedBlocks } =
useSelect( ( select ) => {
const { getInserterSidebarToggleRef, getInsertionPoint } = unlock(
select( editorStore )
);
const { get } = select( preferencesStore );
return {
inserterSidebarToggleRef: getInserterSidebarToggleRef(),
insertionPoint: getInsertionPoint(),
showMostUsedBlocks: get( 'core', 'mostUsedBlocks' ),
};
}, [] );
const { setIsInserterOpened } = useDispatch( editorStore );

const isMobileViewport = useViewportMatch( 'medium', '<' );
const libraryRef = useRef();

// When closing the inserter, focus should return to the toggle button.
const closeInserterSidebar = useCallback( () => {
setIsInserterOpened( false );
inserterSidebarToggleRef.current?.focus();
}, [ inserterSidebarToggleRef, setIsInserterOpened ] );

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

return (
<div className="editor-inserter-sidebar">
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div onKeyDown={ closeOnEscape } className="editor-inserter-sidebar">
<div className="editor-inserter-sidebar__header">
<Button
className="editor-inserter-sidebar__close-button"
icon={ closeSmall }
label={ __( 'Close block inserter' ) }
onClick={ () => setIsInserterOpened( false ) }
onClick={ closeInserterSidebar }
size="small"
/>
</div>
Expand Down
3 changes: 3 additions & 0 deletions packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export const getInsertionPoint = createRegistrySelector( ( select ) =>
export function getListViewToggleRef( state ) {
return state.listViewToggleRef;
}
export function getInserterSidebarToggleRef( state ) {
return state.inserterSidebarToggleRef;
}
const CARD_ICONS = {
wp_block: symbol,
wp_navigation: navigation,
Expand Down
12 changes: 12 additions & 0 deletions packages/editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,17 @@ export function listViewToggleRef( state = { current: null } ) {
return state;
}

/**
* This reducer does nothing aside initializing a ref to the inserter sidebar toggle.
* We will have a unique ref per "editor" instance.
*
* @param {Object} state
* @return {Object} Reference to the inserter sidebar toggle button.
*/
export function inserterSidebarToggleRef( state = { current: null } ) {
return state;
}

export function publishSidebarActive( state = false, action ) {
switch ( action.type ) {
case 'OPEN_PUBLISH_SIDEBAR':
Expand Down Expand Up @@ -387,6 +398,7 @@ export default combineReducers( {
deviceType,
removedPanels,
blockInserterPanel,
inserterSidebarToggleRef,
listViewPanel,
listViewToggleRef,
publishSidebarActive,
Expand Down

0 comments on commit ddaea17

Please sign in to comment.