Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Block Toolbar Semantics/Accessibility #54513

Closed
wants to merge 60 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8e521f7
Move Selected Block Tools into the header toolbar in the DOM but pres…
jeryj Aug 9, 2023
deacf25
Render selected block toolbar as a fill within a slot in the header t…
jeryj Aug 17, 2023
4ede8a7
Pass isFixed to ContextualToolbar for styling
jeryj Aug 9, 2023
45ebf2f
Add block popover to edit site header slot
jeryj Aug 17, 2023
fea3c9f
Add classname to selected block tools slot and use bubblesVirtually
jeryj Aug 17, 2023
ed8541b
Move lastFocus into redux store
jeryj Aug 25, 2023
7728101
Fix comment of getLastFocus method
jeryj Sep 14, 2023
271b0eb
Add inline rich tools to header popover
jeryj Sep 11, 2023
647ecf5
Use menubar role to group header toolbars
jeryj Sep 14, 2023
0f34d47
Add focusEditorOnEscape to NavigableToolbar to return focus to editor
jeryj Sep 14, 2023
55cbde4
Consolodate block tools into one fill component and conditionally ren…
jeryj Sep 14, 2023
6162985
Remove absolute positioned block tool hacks
jeryj Sep 19, 2023
b749291
Add selected block toolbar slot to widget header
jeryj Sep 19, 2023
1cdf89e
Allow block toolbar to scroll if not enough space
jeryj Sep 26, 2023
c15c5b1
Scroll toolbar
jeryj Sep 27, 2023
1e54077
isBlockToolsCollapsed state and styles for edit site header
jeryj Sep 27, 2023
85ac216
Block toolbar scroll on edit site header
jeryj Sep 27, 2023
a6e542e
Only allow block tool collapse to impact large viewports
jeryj Sep 27, 2023
818bdf7
Make room for top toolbar on site editor at smaller screens
jeryj Sep 27, 2023
2364439
Display fixed block toolbar at smaller screen sizes in site editor
jeryj Sep 28, 2023
7551136
Fix select mode in top toolbar
jeryj Sep 28, 2023
6c3dcdf
Add visually hidden aria-describedby instructions for how to use the …
jeryj Sep 29, 2023
ab1c29e
Fix e2e test for shift + tab to editor chrome
jeryj Sep 29, 2023
accedb5
Remove unnecessary test related to shift+tab focusing the block toolbar
jeryj Oct 2, 2023
e24d203
Replace tabbing through block test with arrow through blocks test
jeryj Oct 2, 2023
da3a1ea
Refactor keyboard navigable blocks test to match new behavior
jeryj Oct 2, 2023
484f118
Replace shift+tab with alt+F10 to focus toolbar in tests
jeryj Oct 2, 2023
6addc4f
Pass keydown listener into navigable toolbar instead of removing bubb…
jeryj Oct 3, 2023
9cd01cf
Fix test locator when multiple options buttons are in the header
jeryj Oct 3, 2023
6c80738
Fix test: return to block via escape keypress
jeryj Oct 3, 2023
6bed696
Resizeable box (for resizing cover block) should use after block popo…
jeryj Oct 3, 2023
5ddeb0b
Fix: Add setListViewToggleButton ref back into site edit header
jeryj Oct 3, 2023
6ebb4d9
Update roving toolbar test to use alt+f10 and escape
jeryj Oct 3, 2023
cc0b4dd
Fix selected block tools back compat file path
jeryj Oct 3, 2023
bcfb65b
Clarify getLastFocus description
jeryj Oct 4, 2023
e96401d
Update docs
jeryj Oct 4, 2023
f1066c4
Refactor selectors on selected block popover and empty block inserter
jeryj Oct 4, 2023
92f95f1
Refactoring of selected block tools... continued
jeryj Oct 4, 2023
e565a8a
Refactoring continued
jeryj Oct 4, 2023
b1da15c
Refactor to use useSelectedBlockToolProps
jeryj Oct 4, 2023
bf5e941
Refactor to remove data from useSelectedBlockToolProps that were not …
jeryj Oct 5, 2023
8072e7a
Remove props being overriden in BlockPopover
jeryj Oct 5, 2023
267863a
Fix incorrect block popover positioning
jeryj Oct 5, 2023
ba354bd
Hide block tools popover behind post header
jeryj Oct 5, 2023
d2167e7
Block tools should be behind site editor header
jeryj Oct 5, 2023
d23a4cf
Hide block tools behind header refactoring
jeryj Oct 5, 2023
60a8d69
Only show collapse button if hasFixedToolbar
jeryj Oct 5, 2023
bc03bd1
Fix positioning bug when center tools are collapsed
jeryj Oct 5, 2023
3788c00
Bring back the top toolbar on post editor
jeryj Oct 5, 2023
07ac3b8
Fix widgets top toolbar/block tools slide-behind-header hacks
jeryj Oct 6, 2023
fa398e1
Fixing more positioning hacks with flexbox
jeryj Oct 6, 2023
c3b650c
Add removed __unstableCoverTarget prop back to empty block popover
jeryj Oct 6, 2023
8d57813
Remove unnecessary test
jeryj Oct 6, 2023
4c662de
Handle escape on toolbar via toolbar event listener instead of children
jeryj Oct 6, 2023
0c54480
Add keyboard navigation test to cover escape and alt+10 keypresses
jeryj Oct 6, 2023
a291e1f
Added changelog for popover shiftPadding prop
jeryj Oct 6, 2023
c12381e
Switch to object notation to avoid bugs like I introduced where the o…
jeryj Oct 6, 2023
1449b4a
Fix editor canvas locator evaluation
jeryj Oct 6, 2023
9cc91d9
Show focus ring on document tools
jeryj Oct 6, 2023
3a96e02
Fix check for selectedBlockToolsRef
jeryj Oct 6, 2023
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
24 changes: 24 additions & 0 deletions docs/reference-guides/data/data-core-block-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,18 @@ _Properties_
- _isDisabled_ `boolean`: Whether or not the user should be prevented from inserting this item.
- _frecency_ `number`: Heuristic that combines frequency and recency.

### getLastFocus

Returns the element of the last element that had focus when focus left the editor canvas.
jeryj marked this conversation as resolved.
Show resolved Hide resolved

_Parameters_

- _state_ `Object`: Block editor state.

_Returns_

- `Object`: Element.

### getLastMultiSelectedBlockClientId

Returns the client ID of the last block in the multi-selection set, or null if there is no multi-selection.
Expand Down Expand Up @@ -1651,6 +1663,18 @@ _Parameters_
- _clientId_ `string`: The block's clientId.
- _hasControlledInnerBlocks_ `boolean`: True if the block's inner blocks are controlled.

### setLastFocus

Action that sets the element that had focus when focus leaves the editor canvas.

_Parameters_

- _lastFocus_ `Object`: The last focused element.

_Returns_

- `Object`: Action object.

### setNavigationMode

Action that enables or disables the navigation mode.
Expand Down
3 changes: 1 addition & 2 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ $z-layers: (
// Show interface skeleton footer above interface skeleton drawer
".interface-interface-skeleton__footer": 90,

// Above the block list and the header.
// Below the header background so it can be hidden behind the header.
".block-editor-block-popover": 31,

// Show snackbars above everything (similar to popovers)
".components-snackbar-list": 100000,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import deprecated from '@wordpress/deprecated';
* Internal dependencies
*/
import InsertionPoint, { InsertionPointOpenRef } from './insertion-point';
import BlockPopover from './selected-block-popover';
import BlockPopover from './selected-block-tools';

export default function BlockToolsBackCompat( { children } ) {
const openRef = useContext( InsertionPointOpenRef );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,9 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
useLayoutEffect,
useEffect,
useRef,
useState,
} from '@wordpress/element';
import { forwardRef } from '@wordpress/element';
import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import {
ToolbarItem,
ToolbarButton,
ToolbarGroup,
} from '@wordpress/components';
import { next, previous } from '@wordpress/icons';
import { useViewportMatch } from '@wordpress/compose';

/**
* Internal dependencies
Expand All @@ -31,138 +19,44 @@ import BlockToolbar from '../block-toolbar';
import { store as blockEditorStore } from '../../store';
import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls';

function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
// When the toolbar is fixed it can be collapsed
const [ isCollapsed, setIsCollapsed ] = useState( false );
const toolbarButtonRef = useRef();

const isLargeViewport = useViewportMatch( 'medium' );
const {
blockType,
blockEditingMode,
hasParents,
showParentSelector,
selectedBlockClientId,
} = useSelect( ( select ) => {
const {
getBlockName,
getBlockParents,
getSelectedBlockClientIds,
getBlockEditingMode,
} = select( blockEditorStore );
const { getBlockType } = select( blocksStore );
const selectedBlockClientIds = getSelectedBlockClientIds();
const _selectedBlockClientId = selectedBlockClientIds[ 0 ];
const parents = getBlockParents( _selectedBlockClientId );
const firstParentClientId = parents[ parents.length - 1 ];
const parentBlockName = getBlockName( firstParentClientId );
const parentBlockType = getBlockType( parentBlockName );

return {
selectedBlockClientId: _selectedBlockClientId,
blockType:
_selectedBlockClientId &&
getBlockType( getBlockName( _selectedBlockClientId ) ),
blockEditingMode: getBlockEditingMode( _selectedBlockClientId ),
hasParents: parents.length,
showParentSelector:
parentBlockType &&
getBlockEditingMode( firstParentClientId ) === 'default' &&
hasBlockSupport(
parentBlockType,
'__experimentalParentSelector',
true
) &&
selectedBlockClientIds.length <= 1 &&
getBlockEditingMode( _selectedBlockClientId ) === 'default',
};
}, [] );

useEffect( () => {
setIsCollapsed( false );
}, [ selectedBlockClientId ] );

const isLargerThanTabletViewport = useViewportMatch( 'large', '>=' );
const isFullscreen =
document.body.classList.contains( 'is-fullscreen-mode' );

/**
* The following code is a workaround to fix the width of the toolbar
* it should be removed when the toolbar will be rendered inline
* FIXME: remove this layout effect when the toolbar is no longer
* absolutely positioned
*/
useLayoutEffect( () => {
// don't do anything if not fixed toolbar
if ( ! isFixed ) {
return;
}

const blockToolbar = document.querySelector(
'.block-editor-block-contextual-toolbar'
);

if ( ! blockToolbar ) {
return;
}

if ( ! blockType ) {
blockToolbar.style.width = 'initial';
return;
}

if ( ! isLargerThanTabletViewport ) {
// set the width of the toolbar to auto
blockToolbar.style = {};
return;
}

if ( isCollapsed ) {
// set the width of the toolbar to auto
blockToolbar.style.width = 'auto';
return;
}

// get the width of the pinned items in the post editor or widget editor
const pinnedItems = document.querySelector(
'.edit-post-header__settings, .edit-widgets-header__actions'
);
// get the width of the left header in the site editor
const leftHeader = document.querySelector(
'.edit-site-header-edit-mode__end'
);

const computedToolbarStyle = window.getComputedStyle( blockToolbar );
const computedPinnedItemsStyle = pinnedItems
? window.getComputedStyle( pinnedItems )
: false;
const computedLeftHeaderStyle = leftHeader
? window.getComputedStyle( leftHeader )
: false;

const marginLeft = parseFloat( computedToolbarStyle.marginLeft );
const pinnedItemsWidth = computedPinnedItemsStyle
? parseFloat( computedPinnedItemsStyle.width )
: 0;
const leftHeaderWidth = computedLeftHeaderStyle
? parseFloat( computedLeftHeaderStyle.width )
: 0;

// set the new witdth of the toolbar
blockToolbar.style.width = `calc(100% - ${
leftHeaderWidth +
pinnedItemsWidth +
marginLeft +
( pinnedItems || leftHeader ? 2 : 0 ) + // Prevents button focus border from being cut off
( isFullscreen ? 0 : 160 ) // the width of the admin sidebar expanded
}px)`;
}, [
isFixed,
isLargerThanTabletViewport,
isCollapsed,
isFullscreen,
blockType,
] );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beautiful red.

function UnforwardBlockContextualToolbar(
{ focusOnMount, isFixed, ...props },
ref
) {
const { blockType, blockEditingMode, hasParents, showParentSelector } =
useSelect( ( select ) => {
const {
getBlockName,
getBlockParents,
getSelectedBlockClientIds,
getBlockEditingMode,
} = select( blockEditorStore );
const { getBlockType } = select( blocksStore );
const selectedBlockClientIds = getSelectedBlockClientIds();
const _selectedBlockClientId = selectedBlockClientIds[ 0 ];
const parents = getBlockParents( _selectedBlockClientId );
const firstParentClientId = parents[ parents.length - 1 ];
const parentBlockName = getBlockName( firstParentClientId );
const parentBlockType = getBlockType( parentBlockName );

return {
blockType:
_selectedBlockClientId &&
getBlockType( getBlockName( _selectedBlockClientId ) ),
blockEditingMode: getBlockEditingMode( _selectedBlockClientId ),
hasParents: parents.length,
showParentSelector:
parentBlockType &&
getBlockEditingMode( firstParentClientId ) === 'default' &&
hasBlockSupport(
parentBlockType,
'__experimentalParentSelector',
true
) &&
selectedBlockClientIds.length <= 1 &&
getBlockEditingMode( _selectedBlockClientId ) === 'default',
};
}, [] );

const isToolbarEnabled =
! blockType ||
Expand All @@ -179,45 +73,28 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
const classes = classnames( 'block-editor-block-contextual-toolbar', {
'has-parent': hasParents && showParentSelector,
'is-fixed': isFixed,
'is-collapsed': isCollapsed,
} );

return (
<NavigableToolbar
ref={ ref }
focusOnMount={ focusOnMount }
className={ classes }
/* translators: accessibility text for the block toolbar */
aria-label={ __( 'Block tools' ) }
variant={ isFixed ? 'unstyled' : undefined }
onChildrenKeyDown={ ( event ) => {
if ( event.keyCode === ESCAPE && lastFocus?.current ) {
event.preventDefault();
lastFocus.current.focus();
}
} }
focusEditorOnEscape
{ ...props }
>
{ ! isCollapsed && <BlockToolbar hideDragHandle={ isFixed } /> }
{ isFixed && isLargeViewport && blockType && (
<ToolbarGroup
className={
isCollapsed
? 'block-editor-block-toolbar__group-expand-fixed-toolbar'
: 'block-editor-block-toolbar__group-collapse-fixed-toolbar'
}
>
<ToolbarItem
as={ ToolbarButton }
ref={ toolbarButtonRef }
icon={ isCollapsed ? next : previous }
onClick={ () => {
setIsCollapsed( ( collapsed ) => ! collapsed );
toolbarButtonRef.current.focus();
} }
label={
isCollapsed
? __( 'Show block tools' )
: __( 'Hide block tools' )
}
/>
</ToolbarGroup>
) }
<BlockToolbar hideDragHandle={ isFixed } />
</NavigableToolbar>
);
}

export default BlockContextualToolbar;
export default forwardRef( UnforwardBlockContextualToolbar );
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* Internal dependencies
*/
import BlockPopover from '../block-popover';
import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props';
import Inserter from '../inserter';
import useSelectedBlockToolProps from './use-selected-block-tool-props';

export default function EmptyBlockInserter( {
clientId,
__unstableContentRef,
} ) {
const {
capturingClientId,
isInsertionPointVisible,
lastClientId,
rootClientId,
} = useSelectedBlockToolProps( clientId );
const popoverProps = useBlockToolbarPopoverProps( {
contentElement: __unstableContentRef?.current,
clientId,
} );

return (
<BlockPopover
clientId={ capturingClientId || clientId }
__unstableCoverTarget
bottomClientId={ lastClientId }
className={ classnames(
'block-editor-block-list__block-side-inserter-popover',
{
'is-insertion-point-visible': isInsertionPointVisible,
}
) }
__unstableContentRef={ __unstableContentRef }
{ ...popoverProps }
>
<div className="block-editor-block-list__empty-block-inserter">
<Inserter
position="bottom right"
rootClientId={ rootClientId }
clientId={ clientId }
__experimentalIsQuick
/>
</div>
</BlockPopover>
);
}
Loading
Loading