From ea06ebcdf2cac692f73d0d4537c3331c771798c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:02:16 +0200 Subject: [PATCH] Add `settings` hook (#40547) Co-authored-by: Miguel Fonseca --- packages/block-editor/README.md | 6 +- .../src/components/use-setting/index.js | 78 ++++++++++++++----- packages/block-editor/src/hooks/index.js | 1 + packages/block-editor/src/hooks/settings.js | 32 ++++++++ .../block-editor/src/hooks/test/settings.js | 48 ++++++++++++ packages/block-library/src/group/block.json | 1 + 6 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 packages/block-editor/src/hooks/settings.js create mode 100644 packages/block-editor/src/hooks/test/settings.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index bb4dc1725bc3f..1ac284320078a 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -762,8 +762,10 @@ _Parameters_ ### useSetting -Hook that retrieves the editor setting. -It works with nested objects using by finding the value at path. +Hook that retrieves the given setting for the block instance in use. + +It looks up the settings first in the block instance hierarchy. +If none is found, it'll look it up in the block editor store. _Usage_ diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js index d96546dae73f4..a1aba3e3faf65 100644 --- a/packages/block-editor/src/components/use-setting/index.js +++ b/packages/block-editor/src/components/use-setting/index.js @@ -7,7 +7,10 @@ import { get } from 'lodash'; * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { __EXPERIMENTAL_PATHS_WITH_MERGE as PATHS_WITH_MERGE } from '@wordpress/blocks'; +import { + __EXPERIMENTAL_PATHS_WITH_MERGE as PATHS_WITH_MERGE, + hasBlockSupport, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -91,8 +94,10 @@ const removeCustomPrefixes = ( path ) => { }; /** - * Hook that retrieves the editor setting. - * It works with nested objects using by finding the value at path. + * Hook that retrieves the given setting for the block instance in use. + * + * It looks up the settings first in the block instance hierarchy. + * If none is found, it'll look it up in the block editor store. * * @param {string} path The path to the setting. * @return {any} Returns the value defined for the setting. @@ -102,7 +107,7 @@ const removeCustomPrefixes = ( path ) => { * ``` */ export default function useSetting( path ) { - const { name: blockName } = useBlockEditContext(); + const { name: blockName, clientId } = useBlockEditContext(); const setting = useSelect( ( select ) => { @@ -113,28 +118,59 @@ export default function useSetting( path ) { ); return undefined; } - const settings = select( blockEditorStore ).getSettings(); - // 1 - Use __experimental features, if available. - // We cascade to the all value if the block one is not available. + let result; const normalizedPath = removeCustomPrefixes( path ); - const defaultsPath = `__experimentalFeatures.${ normalizedPath }`; - const blockPath = `__experimentalFeatures.blocks.${ blockName }.${ normalizedPath }`; - const experimentalFeaturesResult = - get( settings, blockPath ) ?? get( settings, defaultsPath ); - if ( experimentalFeaturesResult !== undefined ) { + // 1. Take settings from the block instance or its ancestors. + const candidates = [ + ...select( blockEditorStore ).getBlockParents( clientId ), + clientId, // The current block is added last, so it overwrites any ancestor. + ]; + candidates.forEach( ( candidateClientId ) => { + const candidateBlockName = select( + blockEditorStore + ).getBlockName( candidateClientId ); + if ( + hasBlockSupport( + candidateBlockName, + '__experimentalSettings', + false + ) + ) { + const candidateAtts = select( + blockEditorStore + ).getBlockAttributes( candidateClientId ); + const candidateResult = + get( + candidateAtts, + `settings.blocks.${ blockName }.${ normalizedPath }` + ) ?? + get( candidateAtts, `settings.${ normalizedPath }` ); + if ( candidateResult !== undefined ) { + result = candidateResult; + } + } + } ); + + // 2. Fall back to the settings from the block editor store (__experimentalFeatures). + const settings = select( blockEditorStore ).getSettings(); + if ( result === undefined ) { + const defaultsPath = `__experimentalFeatures.${ normalizedPath }`; + const blockPath = `__experimentalFeatures.blocks.${ blockName }.${ normalizedPath }`; + result = + get( settings, blockPath ) ?? get( settings, defaultsPath ); + } + + // Return if the setting was found in either the block instance or the store. + if ( result !== undefined ) { if ( PATHS_WITH_MERGE[ normalizedPath ] ) { - return ( - experimentalFeaturesResult.custom ?? - experimentalFeaturesResult.theme ?? - experimentalFeaturesResult.default - ); + return result.custom ?? result.theme ?? result.default; } - return experimentalFeaturesResult; + return result; } - // 2 - Use deprecated settings, otherwise. + // 3. Otherwise, use deprecated settings. const deprecatedSettingsValue = deprecatedFlags[ normalizedPath ] ? deprecatedFlags[ normalizedPath ]( settings ) : undefined; @@ -142,13 +178,13 @@ export default function useSetting( path ) { return deprecatedSettingsValue; } - // 3 - Fall back for typography.dropCap: + // 4. Fallback for typography.dropCap: // This is only necessary to support typography.dropCap. // when __experimentalFeatures are not present (core without plugin). // To remove when __experimentalFeatures are ported to core. return normalizedPath === 'typography.dropCap' ? true : undefined; }, - [ blockName, path ] + [ blockName, clientId, path ] ); return setting; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index b76c06ce9bf42..36c3181092a17 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -8,6 +8,7 @@ import './anchor'; import './custom-class-name'; import './generated-class-name'; import './style'; +import './settings'; import './color'; import './duotone'; import './font-size'; diff --git a/packages/block-editor/src/hooks/settings.js b/packages/block-editor/src/hooks/settings.js new file mode 100644 index 0000000000000..a778c1605cbc4 --- /dev/null +++ b/packages/block-editor/src/hooks/settings.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; +import { hasBlockSupport } from '@wordpress/blocks'; + +const hasSettingsSupport = ( blockType ) => + hasBlockSupport( blockType, '__experimentalSettings', false ); + +function addAttribute( settings ) { + if ( ! hasSettingsSupport( settings ) ) { + return settings; + } + + // Allow blocks to specify their own attribute definition with default values if needed. + if ( ! settings?.attributes?.settings ) { + settings.attributes = { + ...settings.attributes, + settings: { + type: 'object', + }, + }; + } + + return settings; +} + +addFilter( + 'blocks.registerBlockType', + 'core/settings/addAttribute', + addAttribute +); diff --git a/packages/block-editor/src/hooks/test/settings.js b/packages/block-editor/src/hooks/test/settings.js new file mode 100644 index 0000000000000..681c635b98271 --- /dev/null +++ b/packages/block-editor/src/hooks/test/settings.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { applyFilters } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import '../settings'; + +describe( 'with settings', () => { + const blockSettings = { + save: () =>
, + category: 'text', + title: 'block title', + }; + + describe( 'addAttribute', () => { + const addAttribute = applyFilters.bind( + null, + 'blocks.registerBlockType' + ); + + it( 'does not have settings att if settings block support is not enabled', () => { + const settings = addAttribute( { + ...blockSettings, + supports: { + __experimentalSettings: false, + }, + } ); + + expect( settings.attributes ).toBe( undefined ); + } ); + + it( 'has settings att if settings block supports is enabled', () => { + const settings = addAttribute( { + ...blockSettings, + supports: { + __experimentalSettings: true, + }, + } ); + + expect( settings.attributes ).toStrictEqual( { + settings: { type: 'object' }, + } ); + } ); + } ); +} ); diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 2e3a99f78ae3c..3e89f14dc938b 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -18,6 +18,7 @@ } }, "supports": { + "__experimentalSettings": true, "align": [ "wide", "full" ], "anchor": true, "html": false,