diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index e28fdafb2f21ec..4e167df21a85c3 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -45,6 +45,7 @@ function selector( select ) { isTyping, isDragging, isZoomOut, + getViewportModalClientIds, } = unlock( select( blockEditorStore ) ); const clientId = @@ -56,6 +57,7 @@ function selector( select ) { isTyping: isTyping(), isZoomOutMode: isZoomOut(), isDragging: isDragging(), + viewportModalClientIds: getViewportModalClientIds(), }; } @@ -73,10 +75,14 @@ export default function BlockTools( { __unstableContentRef, ...props } ) { - const { clientId, hasFixedToolbar, isTyping, isZoomOutMode, isDragging } = - useSelect( selector, [] ); - const [ visibilityModalClientIds, setVisibilityModalClientIds ] = - useState( null ); + const { + clientId, + hasFixedToolbar, + isTyping, + isZoomOutMode, + isDragging, + viewportModalClientIds, + } = useSelect( selector, [] ); const isMatch = useShortcutEventMatch(); const { getBlocksByClientId, @@ -108,6 +114,8 @@ export default function BlockTools( { moveBlocksDown, expandBlock, stopEditingContentOnlySection, + showViewportModal, + hideViewportModal, } = unlock( useDispatch( blockEditorStore ) ); function onKeyDown( event ) { @@ -248,7 +256,7 @@ export default function BlockTools( { } // Open the visibility breakpoints modal. - setVisibilityModalClientIds( clientIds ); + showViewportModal( clientIds ); } } @@ -321,10 +329,10 @@ export default function BlockTools( { onClose={ () => setRenamingBlockClientId( null ) } /> ) } - { visibilityModalClientIds && ( + { viewportModalClientIds && ( setVisibilityModalClientIds( null ) } + clientIds={ viewportModalClientIds } + onClose={ hideViewportModal } /> ) } diff --git a/packages/block-editor/src/components/block-visibility/viewport-menu-item.js b/packages/block-editor/src/components/block-visibility/viewport-menu-item.js index 0d37b4363e880c..349d09dddcb846 100644 --- a/packages/block-editor/src/components/block-visibility/viewport-menu-item.js +++ b/packages/block-editor/src/components/block-visibility/viewport-menu-item.js @@ -3,19 +3,16 @@ */ import { __ } from '@wordpress/i18n'; import { MenuItem } from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies */ -import { BlockVisibilityModal } from './'; import { store as blockEditorStore } from '../../store'; import { unlock } from '../../lock-unlock'; export default function BlockVisibilityViewportMenuItem( { clientIds } ) { - const [ isModalOpen, setIsModalOpen ] = useState( false ); const { areBlocksHiddenAnywhere, shortcut } = useSelect( ( select ) => { const { isBlockHiddenAnywhere } = unlock( @@ -34,20 +31,13 @@ export default function BlockVisibilityViewportMenuItem( { clientIds } ) { }, [ clientIds ] ); + const { showViewportModal } = unlock( useDispatch( blockEditorStore ) ); return ( - <> - setIsModalOpen( true ) } - shortcut={ shortcut } - > - { areBlocksHiddenAnywhere ? __( 'Show' ) : __( 'Hide' ) } - - { isModalOpen && ( - setIsModalOpen( false ) } - /> - ) } - + showViewportModal( clientIds ) } + shortcut={ shortcut } + > + { areBlocksHiddenAnywhere ? __( 'Show' ) : __( 'Hide' ) } + ); } diff --git a/packages/block-editor/src/components/block-visibility/viewport-toolbar.js b/packages/block-editor/src/components/block-visibility/viewport-toolbar.js index 520e219c3ad540..b95a6c90036ac7 100644 --- a/packages/block-editor/src/components/block-visibility/viewport-toolbar.js +++ b/packages/block-editor/src/components/block-visibility/viewport-toolbar.js @@ -3,21 +3,19 @@ */ import { __ } from '@wordpress/i18n'; import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; -import { useRef, useEffect, useState } from '@wordpress/element'; +import { useRef, useEffect } from '@wordpress/element'; import { seen, unseen } from '@wordpress/icons'; import { hasBlockSupport } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import { store as blockEditorStore } from '../../store'; -import { BlockVisibilityModal } from './'; import { unlock } from '../../lock-unlock'; export default function BlockVisibilityViewportToolbar( { clientIds } ) { const hasBlockVisibilityButtonShownRef = useRef( false ); - const [ isModalOpen, setIsModalOpen ] = useState( false ); const { canToggleBlockVisibility, areBlocksHiddenAnywhere } = useSelect( ( select ) => { const { getBlocksByClientId, getBlockName, isBlockHiddenAnywhere } = @@ -39,6 +37,7 @@ export default function BlockVisibilityViewportToolbar( { clientIds } ) { [ clientIds ] ); + const blockEditorDispatch = useDispatch( blockEditorStore ); /* * If the block visibility button has been shown, we don't want to @@ -60,28 +59,19 @@ export default function BlockVisibilityViewportToolbar( { clientIds } ) { return null; } + const { showViewportModal } = unlock( blockEditorDispatch ); + return ( - <> - - setIsModalOpen( true ) } - aria-expanded={ isModalOpen } - aria-haspopup={ ! isModalOpen ? 'dialog' : undefined } - /> - - { isModalOpen && ( - setIsModalOpen( false ) } - /> - ) } - + + showViewportModal( clientIds ) } + aria-haspopup="dialog" + /> + ); } diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index d9cb0518bac123..6b7c7520a92cef 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -54,7 +54,7 @@ import { useBlockRename, BlockRenameModal } from '../block-rename'; import AriaReferencedText from './aria-referenced-text'; import { unlock } from '../../lock-unlock'; import usePasteStyles from '../use-paste-styles'; -import { useBlockVisibility, BlockVisibilityModal } from '../block-visibility'; +import { useBlockVisibility } from '../block-visibility'; import { deviceTypeKey } from '../../store/private-keys'; import { BLOCK_VISIBILITY_VIEWPORTS } from '../block-visibility/constants'; @@ -83,8 +83,6 @@ function ListViewBlock( { const [ isHovered, setIsHovered ] = useState( false ); const [ settingsAnchorRect, setSettingsAnchorRect ] = useState(); const [ isRenameModalOpen, setIsRenameModalOpen ] = useState( false ); - const [ visibilityModalClientIds, setVisibilityModalClientIds ] = - useState( null ); const { isLocked } = useBlockLock( clientId ); const isFirstSelectedBlock = @@ -101,6 +99,7 @@ function ListViewBlock( { removeBlocks, insertAfterBlock, insertBeforeBlock, + showViewportModal, } = unlock( useDispatch( blockEditorStore ) ); const debouncedToggleBlockHighlight = useDebounce( @@ -414,7 +413,7 @@ function ListViewBlock( { } // Open the visibility breakpoints modal. - setVisibilityModalClientIds( blocksToUpdate ); + showViewportModal( blocksToUpdate ); } else if ( isMatch( 'core/block-editor/rename', event ) ) { const { blocksToUpdate } = getBlocksToUpdate(); const isContentOnly = @@ -721,12 +720,6 @@ function ListViewBlock( { ) } ) } - { visibilityModalClientIds && ( - setVisibilityModalClientIds( null ) } - /> - ) } { isRenameModalOpen && ( function useTransformCommands() { @@ -157,19 +160,21 @@ const getQuickActionsCommands = () => getBlockRootClientId, getBlocksByClientId, canRemoveBlocks, - } = useSelect( blockEditorStore ); + isBlockHiddenAnywhere, + } = unlock( useSelect( blockEditorStore ) ); const { getDefaultBlockName, getGroupingBlockName } = useSelect( blocksStore ); const blocks = getBlocksByClientId( clientIds ); + const blockEditorDispatch = useDispatch( blockEditorStore ); const { removeBlocks, replaceBlocks, duplicateBlocks, insertAfterBlock, insertBeforeBlock, - } = useDispatch( blockEditorStore ); + } = blockEditorDispatch; const onGroup = () => { if ( ! blocks.length ) { @@ -204,6 +209,7 @@ const getQuickActionsCommands = () => return { isLoading: false, commands: [] }; } + const { showViewportModal } = unlock( blockEditorDispatch ); const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); const canInsertDefaultBlock = canInsertBlockType( getDefaultBlockName(), @@ -283,6 +289,23 @@ const getQuickActionsCommands = () => } ); } + const supportsVisibility = blocks.every( + ( block ) => + !! block && hasBlockSupport( block.name, 'visibility', true ) + ); + + if ( supportsVisibility ) { + const hasHiddenBlock = clientIds.some( ( id ) => + isBlockHiddenAnywhere( id ) + ); + commands.push( { + name: 'toggle-visibility', + label: hasHiddenBlock ? __( 'Show' ) : __( 'Hide' ), + callback: () => showViewportModal( clientIds ), + icon: hasHiddenBlock ? seen : unseen, + } ); + } + return { isLoading: false, commands: commands.map( ( command ) => ( { diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 82eefc3e941576..daf9880034dff8 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -475,3 +475,28 @@ export function closeListViewContentPanel() { type: 'CLOSE_LIST_VIEW_CONTENT_PANEL', }; } + +/** + * Returns an action object used to open the viewport modal + * for the given client IDs. + * + * @param {string[]} clientIds Client IDs of blocks to configure viewport settings for. + * @return {Object} Action object. + */ +export function showViewportModal( clientIds ) { + return { + type: 'SHOW_VIEWPORT_MODAL', + clientIds, + }; +} + +/** + * Returns an action object used to close the viewport modal. + * + * @return {Object} Action object. + */ +export function hideViewportModal() { + return { + type: 'HIDE_VIEWPORT_MODAL', + }; +} diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 3c46ea9d1989bc..cefc88d074f7ca 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -988,3 +988,15 @@ export function isListViewPanelOpened( state, clientId ) { export function getListViewExpandRevision( state ) { return state.listViewExpandRevision || 0; } + +/** + * Returns the client IDs for the viewport modal, or null if + * the modal is not open. + * + * @param {Object} state Global application state. + * + * @return {string[]|null} Client IDs for the visibility modal, or null. + */ +export function getViewportModalClientIds( state ) { + return state.viewportModalClientIds; +} diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index d7ae3e2ea2e2f4..34672d709eef1f 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1502,6 +1502,25 @@ export function isSelectionEnabled( state = true, action ) { return state; } +/** + * Reducer returning the client IDs for the viewport modal, + * or null if the modal is not open. + * + * @param {string[]|null} state Current state. + * @param {Object} action Dispatched action. + * + * @return {string[]|null} Client IDs for the viewport modal. + */ +export function viewportModalClientIds( state = null, action ) { + switch ( action.type ) { + case 'SHOW_VIEWPORT_MODAL': + return action.clientIds; + case 'HIDE_VIEWPORT_MODAL': + return null; + } + return state; +} + /** * Reducer returning the data needed to display a prompt when certain blocks * are removed, or `false` if no such prompt is requested. @@ -2220,6 +2239,7 @@ const combinedReducers = combineReducers( { lastBlockInserted, editedContentOnlySection, blockVisibility, + viewportModalClientIds, blockEditingModes, styleOverrides, removalPromptData, diff --git a/packages/block-editor/src/store/test/private-actions.js b/packages/block-editor/src/store/test/private-actions.js index a390bdd3939e5c..c97e0218070349 100644 --- a/packages/block-editor/src/store/test/private-actions.js +++ b/packages/block-editor/src/store/test/private-actions.js @@ -9,6 +9,8 @@ import { setInsertionPoint, startDragging, stopDragging, + showViewportModal, + hideViewportModal, } from '../private-actions'; describe( 'private actions', () => { @@ -121,4 +123,22 @@ describe( 'private actions', () => { } ); } ); } ); + + describe( 'showViewportModal', () => { + it( 'should return the SHOW_VIEWPORT_MODAL action with clientIds', () => { + const clientIds = [ 'client-1', 'client-2' ]; + expect( showViewportModal( clientIds ) ).toEqual( { + type: 'SHOW_VIEWPORT_MODAL', + clientIds, + } ); + } ); + } ); + + describe( 'hideViewportModal', () => { + it( 'should return the HIDE_VIEWPORT_MODAL action', () => { + expect( hideViewportModal() ).toEqual( { + type: 'HIDE_VIEWPORT_MODAL', + } ); + } ); + } ); } ); diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 76e1a1a4bccfb5..5fc60b0d728168 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -21,6 +21,7 @@ import { isLockedBlock, isBlockHiddenAnywhere, isBlockHiddenAtViewport, + getViewportModalClientIds, } from '../private-selectors'; import { getBlockEditingMode } from '../selectors'; import { deviceTypeKey } from '../private-keys'; @@ -1321,4 +1322,21 @@ describe( 'private selectors', () => { expect( result ).toBe( false ); } ); } ); + + describe( 'getViewportModalClientIds', () => { + it( 'should return null when modal is not open', () => { + const state = { + viewportModalClientIds: null, + }; + expect( getViewportModalClientIds( state ) ).toBeNull(); + } ); + + it( 'should return client IDs when modal is open', () => { + const clientIds = [ 'client-1', 'client-2' ]; + const state = { + viewportModalClientIds: clientIds, + }; + expect( getViewportModalClientIds( state ) ).toEqual( clientIds ); + } ); + } ); } ); diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 469868078fe2fa..f42ee848594fea 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -42,6 +42,7 @@ import { zoomLevel, editedContentOnlySection, withDerivedBlockEditingModes, + viewportModalClientIds, } from '../reducer'; import { unlock } from '../../lock-unlock'; @@ -4570,4 +4571,35 @@ describe( 'state', () => { } ); } ); } ); + + describe( 'viewportModalClientIds', () => { + it( 'should default to null', () => { + const state = viewportModalClientIds( undefined, {} ); + expect( state ).toBeNull(); + } ); + + it( 'should return clientIds on SHOW_VIEWPORT_MODAL', () => { + const clientIds = [ 'client-1', 'client-2' ]; + const state = viewportModalClientIds( null, { + type: 'SHOW_VIEWPORT_MODAL', + clientIds, + } ); + expect( state ).toEqual( clientIds ); + } ); + + it( 'should return null on HIDE_VIEWPORT_MODAL', () => { + const state = viewportModalClientIds( [ 'client-1' ], { + type: 'HIDE_VIEWPORT_MODAL', + } ); + expect( state ).toBeNull(); + } ); + + it( 'should return current state for unknown actions', () => { + const currentState = [ 'client-1' ]; + const state = viewportModalClientIds( currentState, { + type: 'UNKNOWN_ACTION', + } ); + expect( state ).toBe( currentState ); + } ); + } ); } );