diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index deef2d75386fd5..3f5c782b0717c5 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -519,7 +519,6 @@ function BlockListBlockProvider( props ) { isBlockBeingDragged, hasBlockMovingClientId, canInsertBlockType, - getBlockRootClientId, __unstableHasActiveBlockOverlayActive, __unstableGetEditorMode, getSelectedBlocksInitialCaretPosition, @@ -539,7 +538,6 @@ function BlockListBlockProvider( props ) { getActiveBlockVariation, } = select( blocksStore ); const _isSelected = isBlockSelected( clientId ); - const templateLock = getTemplateLock( rootClientId ); const canRemove = canRemoveBlock( clientId, rootClientId ); const canMove = canMoveBlock( clientId, rootClientId ); const { name: blockName, attributes, isValid } = block; @@ -556,10 +554,12 @@ function BlockListBlockProvider( props ) { const hasLightBlockWrapper = blockType?.apiVersion > 1; const movingClientId = hasBlockMovingClientId(); const blockEditingMode = getBlockEditingMode( clientId ); + return { mode: getBlockMode( clientId ), isSelectionEnabled: isSelectionEnabled(), - isLocked: !! templateLock, + isLocked: !! getTemplateLock( rootClientId ), + templateLock: getTemplateLock( clientId ), canRemove, canMove, // Users of the editor.BlockListBlock filter used to be able to @@ -614,9 +614,12 @@ function BlockListBlockProvider( props ) { movingClientId && canInsertBlockType( getBlockName( movingClientId ), - getBlockRootClientId( clientId ) + rootClientId ), isEditingDisabled: blockEditingMode === 'disabled', + hasEditableOutline: + blockEditingMode !== 'disabled' && + getBlockEditingMode( rootClientId ) === 'disabled', className: hasLightBlockWrapper ? attributes.className : undefined, @@ -660,7 +663,9 @@ function BlockListBlockProvider( props ) { removeOutline, isBlockMovingMode, canInsertMovingBlock, + templateLock, isEditingDisabled, + hasEditableOutline, className, defaultClassName, } = selectedProps; @@ -695,7 +700,9 @@ function BlockListBlockProvider( props ) { removeOutline, isBlockMovingMode, canInsertMovingBlock, + templateLock, isEditingDisabled, + hasEditableOutline, isTemporarilyEditingAsBlocks, defaultClassName, mayDisplayControls, diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index ba1714063fa8fc..f12e63c6d7663e 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -300,35 +300,30 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b } } -// Indicate which blocks are editable within a locked context. -// 1. User must be hovering an editor with renderingMode = 'template-lock'; or... -.is-template-locked:hover, -// ...a container block. -.block-editor-block-list__block:hover { - // 2. Look for locked blocks; or... - .block-editor-block-list__block.is-editing-disabled, - // ...container blocks that have locked children. - &:has(> .block-editor-block-list__block.is-editing-disabled) { - // 3. Highlight any unlocked children of that locked block. - & > .block-editor-block-list__block:not(.is-editing-disabled):not(.is-selected):not(.has-child-selected) { - &::after { - content: ""; - border-style: dotted; - position: absolute; - pointer-events: none; - top: $border-width; - left: $border-width; - right: $border-width; - bottom: $border-width; - border: 1px dotted var(--wp-admin-theme-color); - border-radius: $radius-block-ui - $border-width; - } +@keyframes block-editor-has-editable-outline__fade-out-animation { + from { + border-color: rgba(var(--wp-admin-theme-color--rgb), 1); + } + to { + border-color: rgba(var(--wp-admin-theme-color--rgb), 0); + } +} - &.is-hovered::after { - background: rgba(var(--wp-admin-theme-color--rgb), 0.1); - border: none; - } - } +.is-root-container:not([inert]) .block-editor-block-list__block.has-editable-outline { + &::after { + content: ""; + position: absolute; + pointer-events: none; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 1px dotted rgba(var(--wp-admin-theme-color--rgb), 1); + border-radius: $radius-block-ui; + animation: block-editor-has-editable-outline__fade-out-animation 0.3s ease-out; + animation-delay: 1s; + animation-fill-mode: forwards; + @include reduce-motion("animation"); } } diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 9c7ae30b5997a5..08b43fa46257e4 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -25,6 +25,7 @@ import { useEventHandlers } from './use-selected-block-event-handlers'; import { useNavModeExit } from './use-nav-mode-exit'; import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; +import { useFlashEditableBlocks } from '../../use-flash-editable-blocks'; /** * This hook is used to lightly mark an element as a block element. The element @@ -95,8 +96,10 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isBlockMovingMode, canInsertMovingBlock, isEditingDisabled, + hasEditableOutline, isTemporarilyEditingAsBlocks, defaultClassName, + templateLock, } = useContext( PrivateBlockContext ); // translators: %s: Type of block (i.e. Text, Image etc) @@ -113,6 +116,10 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { useIntersectionObserver(), useMovingAnimation( { triggerAnimationOnChange: index, clientId } ), useDisabled( { isDisabled: ! hasOverlay } ), + useFlashEditableBlocks( { + clientId, + isEnabled: name === 'core/block' || templateLock === 'contentOnly', + } ), ] ); const blockEditContext = useBlockEditContext(); @@ -152,6 +159,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { 'is-block-moving-mode': isBlockMovingMode, 'can-insert-moving-block': canInsertMovingBlock, 'is-editing-disabled': isEditingDisabled, + 'has-editable-outline': hasEditableOutline, 'is-content-locked-temporarily-editing-as-blocks': isTemporarilyEditingAsBlocks, }, diff --git a/packages/block-editor/src/components/use-flash-editable-blocks/index.js b/packages/block-editor/src/components/use-flash-editable-blocks/index.js new file mode 100644 index 00000000000000..34386ea8eadf3b --- /dev/null +++ b/packages/block-editor/src/components/use-flash-editable-blocks/index.js @@ -0,0 +1,62 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +export function useFlashEditableBlocks( { + clientId = '', + isEnabled = true, +} = {} ) { + const { getEnabledClientIdsTree } = unlock( useSelect( blockEditorStore ) ); + + return useRefEffect( + ( element ) => { + if ( ! isEnabled ) { + return; + } + + const flashEditableBlocks = () => { + getEnabledClientIdsTree( clientId ).forEach( + ( { clientId: id } ) => { + const block = element.querySelector( + `[data-block="${ id }"]` + ); + if ( ! block ) { + return; + } + block.classList.remove( 'has-editable-outline' ); + // Force reflow to trigger the animation. + // eslint-disable-next-line no-unused-expressions + block.offsetWidth; + block.classList.add( 'has-editable-outline' ); + } + ); + }; + + const handleClick = ( event ) => { + const shouldFlash = + event.target === element || + event.target.classList.contains( 'is-root-container' ); + if ( ! shouldFlash ) { + return; + } + if ( event.defaultPrevented ) { + return; + } + event.preventDefault(); + flashEditableBlocks(); + }; + + element.addEventListener( 'click', handleClick ); + return () => element.removeEventListener( 'click', handleClick ); + }, + [ isEnabled ] + ); +} diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 74bf4af421dfbb..2f9c5a70952d92 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -24,6 +24,7 @@ import { import { usesContextKey } from './components/rich-text/format-edit'; import { ExperimentalBlockCanvas } from './components/block-canvas'; import { getDuotoneFilter } from './components/duotone/utils'; +import { useFlashEditableBlocks } from './components/use-flash-editable-blocks'; /** * Private @wordpress/block-editor APIs. @@ -52,4 +53,5 @@ lock( privateApis, { ReusableBlocksRenameHint, useReusableBlocksRenameHint, usesContextKey, + useFlashEditableBlocks, } ); diff --git a/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js b/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js index 01947e453aa112..acf13c9025bce3 100644 --- a/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js +++ b/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js @@ -27,27 +27,18 @@ import { store as editorStore } from '../../store'; * editor iframe canvas. */ export default function EditTemplateBlocksNotification( { contentRef } ) { - const { renderingMode, getPostLinkProps, templateId } = useSelect( - ( select ) => { - const { - getRenderingMode, - getEditorSettings, - getCurrentTemplateId, - } = select( editorStore ); - return { - renderingMode: getRenderingMode(), - getPostLinkProps: getEditorSettings().getPostLinkProps, - templateId: getCurrentTemplateId(), - }; - }, - [] - ); - const editTemplate = getPostLinkProps - ? getPostLinkProps( { - postId: templateId, - postType: 'wp_template', - } ) - : {}; + const editTemplate = useSelect( ( select ) => { + const { getEditorSettings, getCurrentTemplateId } = + select( editorStore ); + const { getPostLinkProps } = getEditorSettings(); + return getPostLinkProps + ? getPostLinkProps( { + postId: getCurrentTemplateId(), + postType: 'wp_template', + } ) + : {}; + }, [] ); + const { getNotices } = useSelect( noticesStore ); const { createInfoNotice, removeNotice } = useDispatch( noticesStore ); @@ -58,18 +49,17 @@ export default function EditTemplateBlocksNotification( { contentRef } ) { useEffect( () => { const handleClick = async ( event ) => { - if ( renderingMode !== 'template-locked' ) { - return; - } if ( ! event.target.classList.contains( 'is-root-container' ) ) { return; } + const isNoticeAlreadyShowing = getNotices().some( ( notice ) => notice.id === lastNoticeId.current ); if ( isNoticeAlreadyShowing ) { return; } + const { notice } = await createInfoNotice( __( 'Edit your template to edit this block.' ), { @@ -87,9 +77,6 @@ export default function EditTemplateBlocksNotification( { contentRef } ) { }; const handleDblClick = ( event ) => { - if ( renderingMode !== 'template-locked' ) { - return; - } if ( ! event.target.classList.contains( 'is-root-container' ) ) { return; } @@ -106,7 +93,7 @@ export default function EditTemplateBlocksNotification( { contentRef } ) { canvas?.removeEventListener( 'click', handleClick ); canvas?.removeEventListener( 'dblclick', handleDblClick ); }; - }, [ lastNoticeId, renderingMode, contentRef.current ] ); + }, [ lastNoticeId, contentRef.current ] ); return ( {}; @@ -290,6 +291,9 @@ function EditorCanvas( { const contentRef = useMergeRefs( [ localRef, renderingMode === 'post-only' ? typewriterRef : noop, + useFlashEditableBlocks( { + isEnabled: renderingMode === 'template-locked', + } ), ] ); return ( @@ -364,8 +368,7 @@ function EditorCanvas( { 'is-' + deviceType.toLowerCase() + '-preview', renderingMode !== 'post-only' ? 'wp-site-blocks' - : `${ blockListLayoutClass } wp-block-post-content`, // Ensure root level blocks receive default/flow blockGap styling rules. - renderingMode !== 'all' && 'is-' + renderingMode + : `${ blockListLayoutClass } wp-block-post-content` // Ensure root level blocks receive default/flow blockGap styling rules. ) } layout={ blockListLayout } dropZoneElement={ @@ -377,7 +380,9 @@ function EditorCanvas( { } renderAppender={ renderAppender } /> - + { renderingMode === 'template-locked' && ( + + ) } { children }