From d88dffbebd919eeb9d74c479f4bbbb1a20e5e4c2 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Thu, 30 May 2024 16:18:50 +0200 Subject: [PATCH] Block Variations: Support dot notation in `isActive` string array (#62088) If a block variation's `isActive` property is a `string[]`, support "object paths" (i.e. dot ["property accessors"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors)), such as ```js isActive: [ 'namespace', 'query.postType' ] ``` --------- Co-authored-by: ntsekouras --- .../block-api/block-variations.md | 6 +++ .../block-library/src/query/variations.js | 4 +- packages/blocks/src/store/selectors.js | 26 +++++++++--- packages/blocks/src/store/test/selectors.js | 41 +++++++++++++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/docs/reference-guides/block-api/block-variations.md b/docs/reference-guides/block-api/block-variations.md index 0440858810b65a..0790b7f02641e8 100644 --- a/docs/reference-guides/block-api/block-variations.md +++ b/docs/reference-guides/block-api/block-variations.md @@ -175,6 +175,12 @@ The `string[]` version is used to declare which attributes should be compared as isActive: [ 'providerNameSlug' ] ``` +Nested object paths are also supported. For example, consider a block variation that has a `query` object as an attribute. It is possible to determine if the variation is active solely based on that object's `postType` property (while ignoring all its other properties): + +```js +isActive: [ 'query.postType' ] +``` + ### Caveats to using `isActive` The `isActive` property can return false positives if multiple variations exist for a specific block and the `isActive` checks are not specific enough. To demonstrate this, consider the following example: diff --git a/packages/block-library/src/query/variations.js b/packages/block-library/src/query/variations.js index 65a73d134981f3..1970d9947916b5 100644 --- a/packages/block-library/src/query/variations.js +++ b/packages/block-library/src/query/variations.js @@ -54,9 +54,7 @@ const variations = [ }, }, scope: [ 'inserter' ], - isActive: ( { namespace, query } ) => { - return namespace === 'core/posts-list' && query.postType === 'post'; - }, + isActive: [ 'namespace', 'query.postType' ], }, { name: 'title-date', diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 1fc6eb72d4c378..9b2beb0b8369bd 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -242,16 +242,30 @@ export function getActiveBlockVariation( state, blockName, attributes, scope ) { const blockType = getBlockType( state, blockName ); const attributeKeys = Object.keys( blockType?.attributes || {} ); const definedAttributes = variation.isActive.filter( - ( attribute ) => attributeKeys.includes( attribute ) + ( attribute ) => { + // We support nested attribute paths, e.g. `layout.type`. + // In this case, we need to check if the part before the + // first dot is a known attribute. + const topLevelAttribute = attribute.split( '.' )[ 0 ]; + return attributeKeys.includes( topLevelAttribute ); + } ); if ( definedAttributes.length === 0 ) { return false; } - return definedAttributes.every( - ( attribute ) => - attributes[ attribute ] === - variation.attributes[ attribute ] - ); + return definedAttributes.every( ( attribute ) => { + const attributeValue = getValueFromObjectPath( + attributes, + attribute + ); + if ( attributeValue === undefined ) { + return false; + } + return ( + attributeValue === + getValueFromObjectPath( variation.attributes, attribute ) + ); + } ); } return variation.isActive?.( attributes, variation.attributes ); diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 1fda11d72311a3..5e3a56c34cf3b1 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -410,6 +410,47 @@ describe( 'selectors', () => { expect( result ).toEqual( variation ); } ); } ); + it( 'should support nested attribute paths in the isActive array', () => { + const variations = [ + { + name: 'variation-1', + attributes: { + firstTestAttribute: { + nestedProperty: 1, + otherNestedProperty: 5555, + }, + }, + isActive: [ 'firstTestAttribute.nestedProperty' ], + }, + { + name: 'variation-2', + attributes: { + firstTestAttribute: { + nestedProperty: 2, + otherNestedProperty: 5555, + }, + }, + isActive: [ 'firstTestAttribute.nestedProperty' ], + }, + ]; + const state = + createBlockVariationsStateWithTestBlockType( variations ); + + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: { + nestedProperty: 1, + }, + } ) + ).toEqual( variations[ 0 ] ); + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: { + nestedProperty: 2, + }, + } ) + ).toEqual( variations[ 1 ] ); + } ); it( 'should return the active variation based on the given isActive array (multiple values)', () => { const variations = [ {