UNPKG

@wordpress/block-editor

Version:
246 lines (226 loc) 7.89 kB
/** * WordPress dependencies */ import { useDispatch, useRegistry } from '@wordpress/data'; /** * Internal dependencies */ import { store as blockEditorStore } from '../store'; import { useBlockEditContext } from '../components/block-edit'; const DEFAULT_ATTRIBUTE = '__default'; const PATTERN_OVERRIDES_SOURCE = 'core/pattern-overrides'; const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], 'core/heading': [ 'content' ], 'core/image': [ 'id', 'url', 'title', 'alt', 'caption' ], 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], 'core/post-date': [ 'datetime' ], }; /** * Checks if the given object is empty. * * @param {?Object} object The object to check. * * @return {boolean} Whether the object is empty. */ function isObjectEmpty( object ) { return ! object || Object.keys( object ).length === 0; } /** * Based on the given block name, checks if it is possible to bind the block. * * @param {string} blockName The name of the block. * * @return {boolean} Whether it is possible to bind the block to sources. */ export function canBindBlock( blockName ) { return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; } /** * Based on the given block name and attribute name, checks if it is possible to bind the block attribute. * * @param {string} blockName The name of the block. * @param {string} attributeName The name of attribute. * * @return {boolean} Whether it is possible to bind the block attribute. */ export function canBindAttribute( blockName, attributeName ) { return ( canBindBlock( blockName ) && BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) ); } /** * Gets the bindable attributes for a given block. * * @param {string} blockName The name of the block. * * @return {string[]} The bindable attributes for the block. */ export function getBindableAttributes( blockName ) { return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; } /** * Checks if the block has the `__default` binding for pattern overrides. * * @param {?Record<string, object>} bindings A block's bindings from the metadata attribute. * * @return {boolean} Whether the block has the `__default` binding for pattern overrides. */ export function hasPatternOverridesDefaultBinding( bindings ) { return bindings?.[ DEFAULT_ATTRIBUTE ]?.source === PATTERN_OVERRIDES_SOURCE; } /** * Returns the bindings with the `__default` binding for pattern overrides * replaced with the full-set of supported attributes. e.g.: * * - bindings passed in: `{ __default: { source: 'core/pattern-overrides' } }` * - bindings returned: `{ content: { source: 'core/pattern-overrides' } }` * * @param {string} blockName The block name (e.g. 'core/paragraph'). * @param {?Record<string, object>} bindings A block's bindings from the metadata attribute. * * @return {Object} The bindings with default replaced for pattern overrides. */ export function replacePatternOverridesDefaultBinding( blockName, bindings ) { // The `__default` binding currently only works for pattern overrides. if ( hasPatternOverridesDefaultBinding( bindings ) ) { const supportedAttributes = BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; const bindingsWithDefaults = {}; for ( const attributeName of supportedAttributes ) { // If the block has mixed binding sources, retain any non pattern override bindings. const bindingSource = bindings[ attributeName ] ? bindings[ attributeName ] : { source: PATTERN_OVERRIDES_SOURCE }; bindingsWithDefaults[ attributeName ] = bindingSource; } return bindingsWithDefaults; } return bindings; } /** * Contains utils to update the block `bindings` metadata. * * @typedef {Object} WPBlockBindingsUtils * * @property {Function} updateBlockBindings Updates the value of the bindings connected to block attributes. * @property {Function} removeAllBlockBindings Removes the bindings property of the `metadata` attribute. */ /** * Retrieves the existing utils needed to update the block `bindings` metadata. * They can be used to create, modify, or remove connections from the existing block attributes. * * It contains the following utils: * - `updateBlockBindings`: Updates the value of the bindings connected to block attributes. It can be used to remove a specific binding by setting the value to `undefined`. * - `removeAllBlockBindings`: Removes the bindings property of the `metadata` attribute. * * @since 6.7.0 Introduced in WordPress core. * * @param {?string} clientId Optional block client ID. If not set, it will use the current block client ID from the context. * * @return {?WPBlockBindingsUtils} Object containing the block bindings utils. * * @example * ```js * import { useBlockBindingsUtils } from '@wordpress/block-editor' * const { updateBlockBindings, removeAllBlockBindings } = useBlockBindingsUtils(); * * // Update url and alt attributes. * updateBlockBindings( { * url: { * source: 'core/post-meta', * args: { * key: 'url_custom_field', * }, * }, * alt: { * source: 'core/post-meta', * args: { * key: 'text_custom_field', * }, * }, * } ); * * // Remove binding from url attribute. * updateBlockBindings( { url: undefined } ); * * // Remove bindings from all attributes. * removeAllBlockBindings(); * ``` */ export function useBlockBindingsUtils( clientId ) { const { clientId: contextClientId } = useBlockEditContext(); const blockClientId = clientId || contextClientId; const { updateBlockAttributes } = useDispatch( blockEditorStore ); const { getBlockAttributes } = useRegistry().select( blockEditorStore ); /** * Updates the value of the bindings connected to block attributes. * It removes the binding when the new value is `undefined`. * * @param {Object} bindings Bindings including the attributes to update and the new object. * @param {string} bindings.source The source name to connect to. * @param {Object} [bindings.args] Object containing the arguments needed by the source. * * @example * ```js * import { useBlockBindingsUtils } from '@wordpress/block-editor' * * const { updateBlockBindings } = useBlockBindingsUtils(); * updateBlockBindings( { * url: { * source: 'core/post-meta', * args: { * key: 'url_custom_field', * }, * }, * alt: { * source: 'core/post-meta', * args: { * key: 'text_custom_field', * }, * } * } ); * ``` */ const updateBlockBindings = ( bindings ) => { const { metadata: { bindings: currentBindings, ...metadata } = {} } = getBlockAttributes( blockClientId ); const newBindings = { ...currentBindings }; Object.entries( bindings ).forEach( ( [ attribute, binding ] ) => { if ( ! binding && newBindings[ attribute ] ) { delete newBindings[ attribute ]; return; } newBindings[ attribute ] = binding; } ); const newMetadata = { ...metadata, bindings: newBindings, }; if ( isObjectEmpty( newMetadata.bindings ) ) { delete newMetadata.bindings; } updateBlockAttributes( blockClientId, { metadata: isObjectEmpty( newMetadata ) ? undefined : newMetadata, } ); }; /** * Removes the bindings property of the `metadata` attribute. * * @example * ```js * import { useBlockBindingsUtils } from '@wordpress/block-editor' * * const { removeAllBlockBindings } = useBlockBindingsUtils(); * removeAllBlockBindings(); * ``` */ const removeAllBlockBindings = () => { const { metadata: { bindings, ...metadata } = {} } = getBlockAttributes( blockClientId ); updateBlockAttributes( blockClientId, { metadata: isObjectEmpty( metadata ) ? undefined : metadata, } ); }; return { updateBlockBindings, removeAllBlockBindings }; }