From 97bc820ac29abfc03984b39367cdcab4f3e2be9b Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:12:55 +1100 Subject: [PATCH] List View: Allow right-click to open block settings dropdown, add editor setting (#50273) * List View: Allow right-click to open block settings dropdown, add setting * Fix flicker of focus state when initially right clicking * Tidy up useSelect * Fix e2e test (hopefully) * Update help text for preferences items --- .../list-view/block-select-button.js | 4 ++ .../src/components/list-view/block.js | 72 ++++++++++++++++++- .../src/components/preferences-modal/index.js | 9 +++ packages/edit-post/src/editor.js | 6 ++ packages/edit-post/src/index.js | 1 + .../block-editor/use-site-editor-settings.js | 7 ++ .../src/components/preferences-modal/index.js | 7 ++ packages/edit-site/src/index.js | 1 + .../provider/use-block-editor-settings.js | 1 + test/e2e/specs/editor/various/a11y.spec.js | 25 ++++++- 10 files changed, 130 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js index 25de5483f5192e..6b9de943ea0bf2 100644 --- a/packages/block-editor/src/components/list-view/block-select-button.js +++ b/packages/block-editor/src/components/list-view/block-select-button.js @@ -38,6 +38,8 @@ function ListViewBlockSelectButton( className, block: { clientId }, onClick, + onContextMenu, + onMouseDown, onToggleExpanded, tabIndex, onFocus, @@ -237,7 +239,9 @@ function ListViewBlockSelectButton( className ) } onClick={ onClick } + onContextMenu={ onContextMenu } onKeyDown={ onKeyDownHandler } + onMouseDown={ onMouseDown } ref={ ref } tabIndex={ tabIndex } onFocus={ onFocus } diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 4957f79fa0d481..a90bf116e1d085 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -13,7 +13,13 @@ import { } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { moreVertical } from '@wordpress/icons'; -import { useState, useRef, useCallback, memo } from '@wordpress/element'; +import { + useCallback, + useMemo, + useState, + useRef, + memo, +} from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { sprintf, __ } from '@wordpress/i18n'; import { ESCAPE } from '@wordpress/keycodes'; @@ -53,7 +59,9 @@ function ListViewBlock( { } ) { const cellRef = useRef( null ); const rowRef = useRef( null ); + const settingsRef = useRef( null ); const [ isHovered, setIsHovered ] = useState( false ); + const [ settingsAnchorRect, setSettingsAnchorRect ] = useState(); const { isLocked, canEdit } = useBlockLock( clientId ); @@ -82,6 +90,11 @@ function ListViewBlock( { }, [ clientId ] ); + const allowRightClickOverrides = useSelect( + ( select ) => + select( blockEditorStore ).getSettings().allowRightClickOverrides, + [] + ); const showBlockActions = // When a block hides its toolbar it also hides the block settings menu, @@ -190,6 +203,56 @@ function ListViewBlock( { [ clientId, expand, collapse, isExpanded ] ); + // Allow right-clicking an item in the List View to open up the block settings dropdown. + const onContextMenu = useCallback( + ( event ) => { + if ( showBlockActions && allowRightClickOverrides ) { + settingsRef.current?.click(); + // Ensure the position of the settings dropdown is at the cursor. + setSettingsAnchorRect( + new window.DOMRect( event.clientX, event.clientY, 0, 0 ) + ); + event.preventDefault(); + } + }, + [ allowRightClickOverrides, settingsRef, showBlockActions ] + ); + + const onMouseDown = useCallback( + ( event ) => { + // Prevent right-click from focusing the block, + // because focus will be handled when opening the block settings dropdown. + if ( allowRightClickOverrides && event.button === 2 ) { + event.preventDefault(); + } + }, + [ allowRightClickOverrides ] + ); + + const settingsPopoverAnchor = useMemo( () => { + const { ownerDocument } = rowRef?.current || {}; + + // If no custom position is set, the settings dropdown will be anchored to the + // DropdownMenu toggle button. + if ( ! settingsAnchorRect || ! ownerDocument ) { + return undefined; + } + + // Position the settings dropdown at the cursor when right-clicking a block. + return { + ownerDocument, + getBoundingClientRect() { + return settingsAnchorRect; + }, + }; + }, [ settingsAnchorRect ] ); + + const clearSettingsAnchorRect = useCallback( () => { + // Clear the custom position for the settings dropdown so that it is restored back + // to being anchored to the DropdownMenu toggle button. + setSettingsAnchorRect( undefined ); + }, [ setSettingsAnchorRect ] ); + let colSpan; if ( hasRenderedMovers ) { colSpan = 2; @@ -257,6 +320,8 @@ function ListViewBlock( { { ( { ref, tabIndex, onFocus } ) => ( ) } + + ), }, diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 82014ad06eb493..9dcdb2ce99b5bc 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -52,6 +52,7 @@ export function initializeEditor( id, settings ) { // We dispatch actions and update the store synchronously before rendering // so that we won't trigger unnecessary re-renders with useEffect. dispatch( preferencesStore ).setDefaults( 'core/edit-site', { + allowRightClickOverrides: true, editorMode: 'visual', fixedToolbar: false, focusMode: false, diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index de5d9cf43437d4..34aa472a9921d5 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -29,6 +29,7 @@ const BLOCK_EDITOR_SETTINGS = [ '__unstableGalleryWithImageBlocks', 'alignWide', 'allowedBlockTypes', + 'allowRightClickOverrides', 'blockInspectorTabs', 'allowedMimeTypes', 'bodyPlaceholder', diff --git a/test/e2e/specs/editor/various/a11y.spec.js b/test/e2e/specs/editor/various/a11y.spec.js index 05c4ea3b8e97e3..3ec7318ab89e78 100644 --- a/test/e2e/specs/editor/various/a11y.spec.js +++ b/test/e2e/specs/editor/various/a11y.spec.js @@ -124,6 +124,13 @@ test.describe( 'a11y (@firefox, @webkit)', () => { page, pageUtils, } ) => { + // Note: this test depends on a particular viewport height to determine whether or not + // the modal content is scrollable. If this tests fails and needs to be debugged locally, + // double-check the viewport height when running locally versus in CI. Additionally, + // when adding or removing items from the preference menu, this test may need to be updated + // if the height of panels has changed. It would be good to find a more robust way to test + // this behavior. + // Open the top bar Options menu. await page.click( 'role=region[name="Editor top bar"i] >> role=button[name="Options"i]' @@ -145,6 +152,9 @@ test.describe( 'a11y (@firefox, @webkit)', () => { const generalTab = preferencesModal.locator( 'role=tab[name="General"i]' ); + const accessibilityTab = preferencesModal.locator( + 'role=tab[name="Accessibility"i]' + ); const blocksTab = preferencesModal.locator( 'role=tab[name="Blocks"i]' ); @@ -165,9 +175,20 @@ test.describe( 'a11y (@firefox, @webkit)', () => { await tab.focus(); } - // The General tab panel content is short and not scrollable. - // Check it's not focusable. + // The Accessibility tab is currently short and not scrollable. + // Check that it cannot be focused by tabbing. Note: this test depends + // on a particular viewport height to determine whether or not the + // modal content is scrollable. If additional Accessibility options are + // added, then eventually this test will fail. + // TODO: find a more robust way to test this behavior. await clickAndFocusTab( generalTab ); + // Navigate down to the Accessibility tab. + await pageUtils.pressKeys( 'ArrowDown', { times: 2 } ); + // Check the Accessibility tab panel is visible. + await expect( + preferencesModal.locator( 'role=tabpanel[name="Accessibility"i]' ) + ).toBeVisible(); + await expect( accessibilityTab ).toBeFocused(); await pageUtils.pressKeys( 'Shift+Tab' ); await expect( closeButton ).toBeFocused(); await pageUtils.pressKeys( 'Shift+Tab' );