Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Block Attributes]: Add support for non-duplicable block attributes #32604

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/reference-guides/block-api/block-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ _Example_: Extract `src` and `alt` from each image element in the block's markup
// }
```

## Duplicable Attributes

The `duplicable` key is used to indicate whether a block attribute should be copied when a block is duplicated. If an attribute sets the `duplicable` key to `false`, its value will not be copied, and it will instead fall back to the default value if one is supplied. If the key is not explicitly set, the attribute will be considered duplicable by default.

The `duplicable` key should not be used for attributes that are extracted from block content using an [attribute source](#common-sources).

_Example_: A `resourceId` attribute which should not be copied to duplicate blocks.

```js
{
resourceId: {
type: 'string',
duplicable: false
}
}
```

## Meta (deprecated)

<div class="callout callout-alert">
Expand Down
3 changes: 2 additions & 1 deletion packages/blocks/src/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export function __experimentalCloneSanitizedBlock(
{
...block.attributes,
...mergeAttributes,
}
},
{ shouldRemoveDuplicateAttributes: true }
);

return {
Expand Down
28 changes: 28 additions & 0 deletions packages/blocks/src/api/test/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,34 @@ describe( 'block factory', () => {

expect( clonedBlock.attributes ).toEqual( {} );
} );

it( 'should not duplicate unique attributes, but fallback to available defaults', () => {
registerBlockType( 'core/test-block', {
...defaultBlockSettings,
attributes: {
nonDuplicableAttr: {
type: 'string',
duplicable: false,
},
nonDuplicableAttrWithDefault: {
type: 'string',
duplicable: false,
default: 'default-value',
},
},
} );

const block = createBlock( 'core/test-block', {
nonDuplicableAttr: 'unique-value',
nonDuplicableAttrWithDefault: 'unique-non-default-value',
} );

const clonedBlock = __experimentalCloneSanitizedBlock( block, {} );

expect( clonedBlock.attributes ).toEqual( {
nonDuplicableAttrWithDefault: 'default-value',
} );
} );
} );

describe( 'getPossibleBlockTransformations()', () => {
Expand Down
53 changes: 53 additions & 0 deletions packages/blocks/src/api/test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,59 @@ describe( 'sanitizeBlockAttributes', () => {
} );
} );

it( 'does not strip non-duplicable attributes by default', () => {
registerBlockType( 'core/test-block', {
attributes: {
nonDupicableAttr: {
type: 'string',
duplicable: false,
},
},
title: 'Test block',
} );

const attributes = __experimentalSanitizeBlockAttributes(
'core/test-block',
{
nonDupicableAttr: 'unique-value',
}
);

expect( attributes ).toEqual( {
nonDupicableAttr: 'unique-value',
} );
} );

it( 'removes non-duplicable attributes and falls back to defaults when shouleRemoveDuplicateAttributes is true', () => {
registerBlockType( 'core/test-block', {
attributes: {
nonDuplicableAttr: {
type: 'string',
duplicable: false,
},
nonDuplicableAttrWithDefault: {
type: 'string',
duplicable: false,
default: 'default-value',
},
},
title: 'Test block',
} );

const attributes = __experimentalSanitizeBlockAttributes(
'core/test-block',
{
nonDuplicableAttr: 'unique-value',
nonDuplicableAttrWithDefault: 'unique-non-default-value',
},
{ shouldRemoveDuplicateAttributes: true }
);

expect( attributes ).toEqual( {
nonDuplicableAttrWithDefault: 'default-value',
} );
} );

it( 'handles node and children sources as arrays', () => {
registerBlockType( 'core/test-block', {
attributes: {
Expand Down
27 changes: 23 additions & 4 deletions packages/blocks/src/api/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,16 @@ export function getAccessibleBlockLabel(
* Ensure attributes contains only values defined by block type, and merge
* default values for missing attributes.
*
* @param {string} name The block's name.
* @param {Object} attributes The block's attributes.
* @param {string} name The block's name.
* @param {Object} attributes The block's attributes.
* @param {boolean} shouldRemoveDuplicateAttributes Whether to remove non-duplicable attributes.
* @return {Object} The sanitized attributes.
*/
export function __experimentalSanitizeBlockAttributes( name, attributes ) {
export function __experimentalSanitizeBlockAttributes(
name,
attributes,
{ shouldRemoveDuplicateAttributes = false } = {}
) {
// Get the type definition associated with a registered block.
const blockType = getBlockType( name );

Expand All @@ -255,7 +260,21 @@ export function __experimentalSanitizeBlockAttributes( name, attributes ) {
const value = attributes[ key ];

if ( undefined !== value ) {
accumulator[ key ] = value;
// Remove non-duplicable attributes and merge default values.
if ( shouldRemoveDuplicateAttributes ) {
// An attribute is duplicable by default if not specified in the schema.
const duplicable =
! schema.hasOwnProperty( 'duplicable' ) ||
schema.duplicable;

if ( duplicable ) {
accumulator[ key ] = value;
} else if ( schema.hasOwnProperty( 'default' ) ) {
accumulator[ key ] = schema.default;
}
} else {
accumulator[ key ] = value;
}
} else if ( schema.hasOwnProperty( 'default' ) ) {
accumulator[ key ] = schema.default;
}
Expand Down