diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 0fac97b3372369..955da5a92d147e 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -43,7 +43,7 @@ Create and save content to reuse across your site. Update the pattern, and the c
- **Name:** core/block
- **Category:** reusable
- **Supports:** ~~customClassName~~, ~~html~~, ~~inserter~~
-- **Attributes:** dynamicContent, patternId, ref
+- **Attributes:** dynamicContent, ref
## Button
diff --git a/lib/block-supports/pattern.php b/lib/block-supports/pattern.php
index 174aa5949c48d7..069eb5e84c6ced 100644
--- a/lib/block-supports/pattern.php
+++ b/lib/block-supports/pattern.php
@@ -8,7 +8,7 @@
$gutenberg_experiments = get_option( 'gutenberg-experiments' );
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-patterns', $gutenberg_experiments ) ) {
/**
- * Adds `patternId` and `dynamicContent` items to the block's `usesContext`
+ * Adds `dynamicContent` items to the block's `usesContext`
* configuration.
*
* @param WP_Block_Type $block_type Block type.
@@ -21,10 +21,6 @@ function gutenberg_register_pattern_support( $block_type ) {
$block_type->uses_context = array();
}
- if ( ! in_array( 'patternId', $block_type->uses_context, true ) ) {
- $block_type->uses_context[] = 'patternId';
- }
-
if ( ! in_array( 'dynamicContent', $block_type->uses_context, true ) ) {
$block_type->uses_context[] = 'dynamicContent';
}
diff --git a/packages/block-editor/src/hooks/pattern.js b/packages/block-editor/src/hooks/pattern.js
index 8def099c7eee71..b793d6e7200ecd 100644
--- a/packages/block-editor/src/hooks/pattern.js
+++ b/packages/block-editor/src/hooks/pattern.js
@@ -1,163 +1,55 @@
-/**
- * External dependencies
- */
-// eslint-disable-next-line import/no-extraneous-dependencies
-import { v4 as uuid } from 'uuid';
-// TODO: Fix the unique ID generation to avoid adding another dependency just for this.
-
/**
* WordPress dependencies
*/
-import { getBlockSupport, getBlockType } from '@wordpress/blocks';
+import { store as blocksStore } from '@wordpress/blocks';
import { createHigherOrderComponent } from '@wordpress/compose';
-import { useDispatch, useRegistry } from '@wordpress/data';
-import { useCallback, useMemo } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
+import { useMemo } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
/**
* Internal dependencies
*/
-import { cleanEmptyObject } from './utils';
+import { useBlockEditContext } from '../components/block-edit';
import { store as blockEditorStore } from '../store';
export const PATTERN_SUPPORT_KEY = '__experimentalPattern';
-function hasPatternSupport( blockType ) {
- return !! getBlockSupport( blockType, PATTERN_SUPPORT_KEY );
-}
-
// TODO: Extract this to a custom source file?
-function useSource( {
- name,
- context: { dynamicContent, patternId },
- attributes,
- setAttributes,
-} ) {
- const { updateBlockAttributes } = useDispatch( blockEditorStore );
- const blockType = getBlockType( name );
- const patternSupport = blockType.supports?.[ PATTERN_SUPPORT_KEY ];
- // Generate unique id to link the block instance with the data in the
- // pattern block's dynamic content.
+function useSourceAttributes( attributes ) {
+ const { name, clientId } = useBlockEditContext();
+ const dynamicContent = useSelect(
+ ( select ) => {
+ const hasPatternSupport = select( blocksStore ).hasBlockSupport(
+ name,
+ PATTERN_SUPPORT_KEY
+ );
+ if ( ! hasPatternSupport ) return undefined;
+ const parentPatternClientId = select(
+ blockEditorStore
+ ).getBlockParentsByBlockName( clientId, 'core/block', true )[ 0 ];
+ if ( ! parentPatternClientId ) return undefined;
+ return select( blockEditorStore ).getBlockAttributes(
+ parentPatternClientId
+ ).dynamicContent;
+ },
+ [ name, clientId ]
+ );
const attributesWithSourcedAttributes = useMemo( () => {
const id = attributes.metadata?.id;
- if ( ! patternSupport || ! id || ! dynamicContent ) {
+ if ( ! id || ! dynamicContent ) {
return attributes;
}
return {
...attributes,
- ...Object.fromEntries(
- Object.keys( patternSupport ).map( ( attributeName ) => {
- return [
- attributeName,
- dynamicContent[ id ]?.[ attributeName ] ||
- attributes[ attributeName ],
- ];
- } )
- ),
+ ...dynamicContent[ id ],
};
- }, [ attributes, dynamicContent, patternSupport ] );
-
- const updatedSetAttributes = useCallback(
- ( nextAttributes ) => {
- const id = attributes.metadata?.id ?? uuid();
-
- // Collect the updated dynamic content for the current block.
- const updatedDynamicContent = Object.entries( nextAttributes ?? {} )
- .filter(
- ( [ key ] ) => patternSupport && key in patternSupport
- )
- .map( ( [ key, value ] ) => {
- if ( value === '' ) {
- return [ key, undefined ];
- }
-
- return [ key, value ];
- } );
-
- // Collect the updated dynamic pattern content.
- const nextDynamicContent = cleanEmptyObject( {
- ...dynamicContent,
- [ id ]: {
- ...dynamicContent?.[ id ],
- ...Object.fromEntries( updatedDynamicContent ),
- },
- } );
-
- // Filter out pattern stored attributes so they don't override the
- // original attributes that act as a default or fallback.
- const updatedAttributes = updatedDynamicContent.length
- ? Object.fromEntries(
- Object.entries( nextAttributes ?? {} ).filter(
- ( [ key ] ) =>
- ! ( patternSupport && key in patternSupport )
- )
- )
- : nextAttributes;
-
- // Update the parent pattern instance's dynamic content attribute.
- if ( updatedDynamicContent.length ) {
- // If we are setting dynamic content on the parent pattern,
- // ensure the block's id is saved in its metadata.
- if ( ! attributes.metadata?.id ) {
- updatedAttributes.metadata = {
- ...updatedAttributes.metadata,
- id,
- };
- }
-
- updateBlockAttributes( patternId, {
- dynamicContent: nextDynamicContent,
- } );
- }
-
- setAttributes( updatedAttributes );
- },
- [
- attributes.metadata?.id,
- dynamicContent,
- patternId,
- patternSupport,
- setAttributes,
- updateBlockAttributes,
- ]
- );
+ }, [ attributes, dynamicContent ] );
- return {
- attributes: attributesWithSourcedAttributes,
- setAttributes: updatedSetAttributes,
- };
-}
-
-/**
- * Filters registered block settings, extending usesContext to include the
- * dynamic content and setter provided by a pattern block.
- *
- * @param {Object} settings Original block settings.
- *
- * @return {Object} Filtered block settings.
- */
-function extendUsesContext( settings ) {
- if ( ! hasPatternSupport( settings ) ) {
- return settings;
- }
-
- if ( ! Array.isArray( settings.usesContext ) ) {
- settings.usesContext = [ 'dynamicContent', 'patternId' ];
- return settings;
- }
-
- if ( ! settings.usesContext.includes( 'dynamicContent' ) ) {
- settings.usesContext.push( 'dynamicContent' );
- }
-
- if ( ! settings.usesContext.includes( 'patternId' ) ) {
- settings.usesContext.push( 'patternId' );
- }
-
- return settings;
+ return attributesWithSourcedAttributes;
}
/**
@@ -172,37 +64,11 @@ function extendUsesContext( settings ) {
const createEditFunctionWithPatternSource = () =>
createHigherOrderComponent(
( BlockEdit ) =>
- ( { name, attributes, setAttributes, context, ...props } ) => {
- if ( ! context.patternId ) {
- return (
-
- );
- }
-
- const registry = useRegistry();
- const {
- attributes: updatedAttributes,
- setAttributes: updatedSetAttributes,
- } = useSource( { name, attributes, setAttributes, context } );
+ ( { attributes, ...props } ) => {
+ const sourceAttributes = useSourceAttributes( attributes );
return (
-
- registry.batch( () =>
- updatedSetAttributes( newAttributes )
- )
- }
- { ...props }
- />
+
);
}
);
@@ -219,10 +85,4 @@ if ( window.__experimentalPatterns ) {
'core/pattern/shimAttributeSource',
shimAttributeSource
);
-
- addFilter(
- 'blocks.registerBlockType',
- 'core/pattern/extendUsesContext',
- extendUsesContext
- );
}
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index 32108de713f754..57926a9c23614f 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -13,6 +13,7 @@ import {
switchToBlockType,
synchronizeBlocksWithTemplate,
getBlockSupport,
+ store as blocksStore,
} from '@wordpress/blocks';
import { speak } from '@wordpress/a11y';
import { __, _n, sprintf } from '@wordpress/i18n';
@@ -157,18 +158,96 @@ export function receiveBlocks( blocks ) {
* @param {boolean} uniqueByBlock true if each block in clientIds array has a unique set of attributes
* @return {Object} Action object.
*/
-export function updateBlockAttributes(
- clientIds,
- attributes,
- uniqueByBlock = false
-) {
- return {
- type: 'UPDATE_BLOCK_ATTRIBUTES',
- clientIds: castArray( clientIds ),
- attributes,
- uniqueByBlock,
+export const updateBlockAttributes =
+ ( clientIds, attributes, uniqueByBlock = false ) =>
+ ( { select, dispatch, registry } ) => {
+ if ( ! window.__experimentalPatterns ) {
+ dispatch( {
+ type: 'UPDATE_BLOCK_ATTRIBUTES',
+ clientIds: castArray( clientIds ),
+ attributes,
+ uniqueByBlock,
+ } );
+ return;
+ }
+
+ const updates = {};
+ for ( const clientId of castArray( clientIds ) ) {
+ const attrs = uniqueByBlock ? attributes[ clientId ] : attributes;
+ const parentBlocks = select.getBlocksByClientId(
+ select.getBlockParents( clientId )
+ );
+ const parentPattern = parentBlocks.findLast(
+ ( parentBlock ) => parentBlock.name === 'core/block'
+ );
+ const block = select.getBlock( clientId );
+ if (
+ ! parentPattern ||
+ ! registry
+ .select( blocksStore )
+ .hasBlockSupport( block.name, '__experimentalPattern' )
+ ) {
+ updates[ clientId ] = attrs;
+ continue;
+ }
+
+ const contentAttributes = registry
+ .select( blocksStore )
+ .getBlockSupport( block.name, '__experimentalPattern' );
+ const dynamicContent = {};
+ const updatedAttributes = {};
+ for ( const attributeKey of Object.keys( attrs ) ) {
+ if ( Object.hasOwn( contentAttributes, attributeKey ) ) {
+ dynamicContent[ attributeKey ] = attrs[ attributeKey ];
+ } else {
+ updatedAttributes[ attributeKey ] = attrs[ attributeKey ];
+ }
+ }
+ if ( Object.keys( dynamicContent ).length > 0 ) {
+ let id = block.attributes.metadata?.id;
+ if ( ! id ) {
+ // The id just has to be unique within the pattern context, so we
+ // use the block's clientId as a convenient unique identifier.
+ id = block.clientId;
+ updatedAttributes.metadata = {
+ ...block.attributes.metadata,
+ id,
+ };
+ }
+
+ updates[ parentPattern.clientId ] = {
+ dynamicContent: {
+ ...parentPattern.attributes.dynamicContent,
+ [ id ]: dynamicContent,
+ },
+ };
+ }
+ if ( Object.keys( updatedAttributes ).length > 0 ) {
+ updates[ clientId ] = updatedAttributes;
+ }
+ }
+
+ if (
+ Object.values( updates ).every(
+ ( updatedAttributes, _index, arr ) =>
+ updatedAttributes === arr[ 0 ]
+ )
+ ) {
+ dispatch( {
+ type: 'UPDATE_BLOCK_ATTRIBUTES',
+ clientIds: Object.keys( updates ),
+ attributes: Object.values( updates )[ 0 ],
+ uniqueByBlock: false,
+ } );
+ } else {
+ dispatch( {
+ type: 'UPDATE_BLOCK_ATTRIBUTES',
+ clientIds: Object.keys( updates ),
+ attributes: updates,
+ uniqueByBlock: true,
+ } );
+ }
};
-}
/**
* Action that updates the block with the specified client ID.
diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json
index 3b1508a362cca9..d66234f9b5134b 100644
--- a/packages/block-library/src/block/block.json
+++ b/packages/block-library/src/block/block.json
@@ -11,17 +11,10 @@
"dynamicContent": {
"type": "object"
},
- "patternId": {
- "type": "string"
- },
"ref": {
"type": "number"
}
},
- "providesContext": {
- "dynamicContent": "dynamicContent",
- "patternId": "patternId"
- },
"supports": {
"customClassName": false,
"html": false,
diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js
index 21586854fb6d05..13745ae0fd6dec 100644
--- a/packages/block-library/src/block/edit.js
+++ b/packages/block-library/src/block/edit.js
@@ -19,30 +19,11 @@ import {
__experimentalUseHasRecursion as useHasRecursion,
InnerBlocks,
InspectorControls,
- store as blockEditorStore,
useBlockProps,
Warning,
} from '@wordpress/block-editor';
-import { useDispatch } from '@wordpress/data';
-import { useEffect } from '@wordpress/element';
-
-export default function ReusableBlockEdit( {
- attributes: { ref },
- clientId,
- setAttributes,
-} ) {
- const { __unstableMarkNextChangeAsNotPersistent } =
- useDispatch( blockEditorStore );
-
- // To leverage updateBlockAttributes in the pattern block support we need
- // an ID, otherwise we'd need to pass a setter down through context.
- // The `clientId` is a prop and so can't be passed directly through block
- // context. Instead, we set this on a dedicated block attribute.
- useEffect( () => {
- __unstableMarkNextChangeAsNotPersistent();
- setAttributes( { patternId: clientId } );
- }, [] );
+export default function ReusableBlockEdit( { attributes: { ref } } ) {
const hasAlreadyRendered = useHasRecursion( ref );
const { record, hasResolved } = useEntityRecord(
'postType',