diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 92da4b8719632..762b3a96a87a9 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -29,6 +29,7 @@ import { useFlashEditableBlocks } from './components/use-flash-editable-blocks'; import { selectBlockPatternsKey } from './store/private-keys'; import { requiresWrapperOnCopy } from './components/writing-flow/utils'; import { PrivateRichText } from './components/rich-text/'; +import { BlockRenameModal } from './components/block-rename'; /** * Private @wordpress/block-editor APIs. @@ -62,4 +63,5 @@ lock( privateApis, { selectBlockPatternsKey, requiresWrapperOnCopy, PrivateRichText, + BlockRenameModal, } ); diff --git a/packages/editor/src/hooks/pattern-overrides.js b/packages/editor/src/hooks/pattern-overrides.js index 442ce70a2bf71..3c90a32134540 100644 --- a/packages/editor/src/hooks/pattern-overrides.js +++ b/packages/editor/src/hooks/pattern-overrides.js @@ -14,7 +14,7 @@ import { store as editorStore } from '../store'; import { unlock } from '../lock-unlock'; const { - useSetPatternBindings, + PatternOverridesControls, ResetOverridesControl, PATTERN_TYPES, PARTIAL_SYNCING_SUPPORTED_BLOCKS, @@ -38,7 +38,6 @@ const withPatternOverrideControls = createHigherOrderComponent( return ( <> - { isSupportedBlock && } { props.isSelected && isSupportedBlock && ( ) } @@ -47,15 +46,6 @@ const withPatternOverrideControls = createHigherOrderComponent( } ); -function BindingUpdater( props ) { - const postType = useSelect( - ( select ) => select( editorStore ).getCurrentPostType(), - [] - ); - useSetPatternBindings( props, postType ); - return null; -} - // Split into a separate component to avoid a store subscription // on every block. function ControlsWithStoreSubscription( props ) { @@ -73,6 +63,8 @@ function ControlsWithStoreSubscription( props ) { ( binding ) => binding.source === 'core/pattern-overrides' ); + const shouldShowPatternOverridesControls = + isEditingPattern && blockEditingMode === 'default'; const shouldShowResetOverridesControl = ! isEditingPattern && !! props.attributes.metadata?.name && @@ -81,6 +73,9 @@ function ControlsWithStoreSubscription( props ) { return ( <> + { shouldShowPatternOverridesControls && ( + + ) } { shouldShowResetOverridesControl && ( ) } diff --git a/packages/patterns/src/components/pattern-overrides-controls.js b/packages/patterns/src/components/pattern-overrides-controls.js new file mode 100644 index 0000000000000..605eb92fd9ecc --- /dev/null +++ b/packages/patterns/src/components/pattern-overrides-controls.js @@ -0,0 +1,122 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { + InspectorControls, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { BaseControl, CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../constants'; +import { unlock } from '../lock-unlock'; + +const { BlockRenameModal } = unlock( blockEditorPrivateApis ); + +function removeBindings( bindings, syncedAttributes ) { + let updatedBindings = {}; + for ( const attributeName of syncedAttributes ) { + // Omit any pattern override bindings from the `updatedBindings` object. + if ( + bindings?.[ attributeName ]?.source !== 'core/pattern-overrides' && + bindings?.[ attributeName ]?.source !== undefined + ) { + updatedBindings[ attributeName ] = bindings[ attributeName ]; + } + } + if ( ! Object.keys( updatedBindings ).length ) { + updatedBindings = undefined; + } + return updatedBindings; +} + +function addBindings( bindings, syncedAttributes ) { + const updatedBindings = { ...bindings }; + for ( const attributeName of syncedAttributes ) { + if ( ! bindings?.[ attributeName ] ) { + updatedBindings[ attributeName ] = { + source: 'core/pattern-overrides', + }; + } + } + return updatedBindings; +} + +function PatternOverridesControls( { attributes, name, setAttributes } ) { + const [ showBlockNameModal, setShowBlockNameModal ] = useState( false ); + + const syncedAttributes = PARTIAL_SYNCING_SUPPORTED_BLOCKS[ name ]; + const attributeSources = syncedAttributes.map( + ( attributeName ) => + attributes.metadata?.bindings?.[ attributeName ]?.source + ); + const isConnectedToOtherSources = attributeSources.every( + ( source ) => source && source !== 'core/pattern-overrides' + ); + + function updateBindings( isChecked, customName ) { + if ( isChecked && ! attributes.metadata?.name && ! customName ) { + setShowBlockNameModal( true ); + return; + } + + const prevBindings = attributes?.metadata?.bindings; + const updatedBindings = isChecked + ? addBindings( prevBindings, syncedAttributes ) + : removeBindings( prevBindings, syncedAttributes ); + + const updatedMetadata = { + ...attributes.metadata, + bindings: updatedBindings, + }; + + if ( customName ) { + updatedMetadata.name = customName; + } + + setAttributes( { + metadata: updatedMetadata, + } ); + } + + // Avoid overwriting other (e.g. meta) bindings. + if ( isConnectedToOtherSources ) return null; + + return ( + <> + + + + { __( 'Pattern overrides' ) } + + source === 'core/pattern-overrides' + ) } + onChange={ ( isChecked ) => { + updateBindings( isChecked ); + } } + /> + + + + { showBlockNameModal && ( + setShowBlockNameModal( false ) } + onSave={ ( newName ) => { + updateBindings( true, newName ); + } } + /> + ) } + + ); +} + +export default PatternOverridesControls; diff --git a/packages/patterns/src/components/use-set-pattern-bindings.js b/packages/patterns/src/components/use-set-pattern-bindings.js deleted file mode 100644 index df16d2b2b0591..0000000000000 --- a/packages/patterns/src/components/use-set-pattern-bindings.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * WordPress dependencies - */ -import { usePrevious } from '@wordpress/compose'; -import { useEffect } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../constants'; - -function removeBindings( bindings, syncedAttributes ) { - let updatedBindings = {}; - for ( const attributeName of syncedAttributes ) { - // Omit any pattern override bindings from the `updatedBindings` object. - if ( - bindings?.[ attributeName ]?.source !== 'core/pattern-overrides' && - bindings?.[ attributeName ]?.source !== undefined - ) { - updatedBindings[ attributeName ] = bindings[ attributeName ]; - } - } - if ( ! Object.keys( updatedBindings ).length ) { - updatedBindings = undefined; - } - return updatedBindings; -} - -function addBindings( bindings, syncedAttributes ) { - const updatedBindings = { ...bindings }; - for ( const attributeName of syncedAttributes ) { - if ( ! bindings?.[ attributeName ] ) { - updatedBindings[ attributeName ] = { - source: 'core/pattern-overrides', - }; - } - } - return updatedBindings; -} - -export default function useSetPatternBindings( - { name, attributes, setAttributes }, - currentPostType -) { - const metadataName = attributes?.metadata?.name ?? ''; - const prevMetadataName = usePrevious( metadataName ) ?? ''; - const bindings = attributes?.metadata?.bindings; - - useEffect( () => { - // Bindings should only be created when editing a wp_block post type, - // and also when there's a change to the user-given name for the block. - if ( - currentPostType !== 'wp_block' || - metadataName === prevMetadataName - ) { - return; - } - - const syncedAttributes = PARTIAL_SYNCING_SUPPORTED_BLOCKS[ name ]; - const attributeSources = syncedAttributes.map( - ( attributeName ) => - attributes.metadata?.bindings?.[ attributeName ]?.source - ); - const isConnectedToOtherSources = attributeSources.every( - ( source ) => source && source !== 'core/pattern-overrides' - ); - - // Avoid overwriting other (e.g. meta) bindings. - if ( isConnectedToOtherSources ) { - return; - } - - // The user-given name for the block was deleted, remove the bindings. - if ( ! metadataName?.length && prevMetadataName?.length ) { - const updatedBindings = removeBindings( - bindings, - syncedAttributes - ); - setAttributes( { - metadata: { - ...attributes.metadata, - bindings: updatedBindings, - }, - } ); - } - - // The user-given name for the block was set, set the bindings. - if ( ! prevMetadataName?.length && metadataName.length ) { - const updatedBindings = addBindings( bindings, syncedAttributes ); - setAttributes( { - metadata: { - ...attributes.metadata, - bindings: updatedBindings, - }, - } ); - } - }, [ - bindings, - prevMetadataName, - metadataName, - currentPostType, - name, - attributes.metadata, - setAttributes, - ] ); -} diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index 54ad5a4aa47d1..817a3413405ee 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -13,7 +13,7 @@ import { import RenamePatternModal from './components/rename-pattern-modal'; import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; -import useSetPatternBindings from './components/use-set-pattern-bindings'; +import PatternOverridesControls from './components/pattern-overrides-controls'; import ResetOverridesControl from './components/reset-overrides-control'; import { useAddPatternCategory } from './private-hooks'; import { @@ -34,7 +34,7 @@ lock( privateApis, { RenamePatternModal, PatternsMenuItems, RenamePatternCategoryModal, - useSetPatternBindings, + PatternOverridesControls, ResetOverridesControl, useAddPatternCategory, PATTERN_TYPES, diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 4542e8c789ad1..e191a845492c4 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -87,6 +87,9 @@ test.describe( 'Pattern Overrides', () => { await editorSettings .getByRole( 'textbox', { name: 'Block Name' } ) .fill( editableParagraphName ); + await editorSettings + .getByRole( 'checkbox', { name: 'Allow instance overrides' } ) + .setChecked( true ); await expect.poll( editor.getBlocks ).toMatchObject( [ {