diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index f035e026ff2e11..cc841ad59021e8 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -704,6 +704,19 @@ attributes: { } ``` +## role + +- Type: `boolean` +- Default value: `false` + +Role attributes let you add semantic meaning to non-semantic HTML elements. This property allows enabling the definition of a role for the block, without exposing a UI field. + +```js +supports: { + role: true, +} +``` + ## spacing - Type: `Object` diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 02c6c397d924ca..e022adcf7d539b 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -341,7 +341,7 @@ Gather blocks in a layout container. ([Source](https://github.com/WordPress/gute - **Name:** core/group - **Category:** design -- **Supports:** align (full, wide), anchor, ariaLabel, background (backgroundImage, backgroundSize), color (background, button, gradients, heading, link, text), dimensions (aspectRatio, minHeight), layout (allowSizingOnChildren), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), anchor, ariaLabel, background (backgroundImage, backgroundSize), color (background, button, gradients, heading, link, text), dimensions (aspectRatio, minHeight), layout (allowSizingOnChildren), position (sticky), role, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** allowedBlocks, tagName, templateLock ## Heading diff --git a/lib/block-supports/role.php b/lib/block-supports/role.php new file mode 100644 index 00000000000000..fc364b8d1f9d2e --- /dev/null +++ b/lib/block-supports/role.php @@ -0,0 +1,69 @@ +supports, array( 'role' ), true ); + if ( ! $has_role_support ) { + return; + } + + if ( ! $block_type->attributes ) { + $block_type->attributes = array(); + } + + if ( ! array_key_exists( 'role', $block_type->attributes ) ) { + $block_type->attributes['role'] = array( + 'type' => 'string', + ); + } +} + +/** + * Add the role attribute to the output. + * + * @param WP_Block_Type $block_type Block Type. + * @param array $block_attributes Block attributes. + * + * @return array Block role attribute. + */ +function gutenberg_apply_role_support( $block_type, $block_attributes ) { + // Return early if the block doesn't have any attributes. + if ( ! $block_attributes ) { + return array(); + } + // Don't print the role in the block wrapper if its set to skip serilization. + if ( wp_should_skip_block_supports_serialization( $block_type, 'role' ) ) { + return array(); + } + // Return early if the block doesn't have support for role. + $has_role_support = _wp_array_get( $block_type->supports, array( 'role' ), true ); + if ( ! $has_role_support ) { + return array(); + } + // Return early if the block doesn't have a role attribute. + $has_role = array_key_exists( 'role', $block_attributes ); + if ( ! $has_role ) { + return array(); + } + + return array( 'role' => $block_attributes['role'] ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'role', + array( + 'register_attribute' => 'gutenberg_register_role_support', + 'apply' => 'gutenberg_apply_role_support', + ) +); diff --git a/lib/load.php b/lib/load.php index 4b2b4d5d8b0db8..9bcb2b2e4962e9 100644 --- a/lib/load.php +++ b/lib/load.php @@ -207,6 +207,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/shadow.php'; require __DIR__ . '/block-supports/background.php'; +require __DIR__ . '/block-supports/role.php'; // Data views. require_once __DIR__ . '/experimental/data-views.php'; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index e3c0a7580aab3c..25ac2e8d39b9c6 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -28,6 +28,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; +import './role'; import './use-bindings-attributes'; createBlockEditFilter( diff --git a/packages/block-editor/src/hooks/role.js b/packages/block-editor/src/hooks/role.js new file mode 100644 index 00000000000000..1b5f36860a519b --- /dev/null +++ b/packages/block-editor/src/hooks/role.js @@ -0,0 +1,61 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; +import { hasBlockSupport } from '@wordpress/blocks'; + +const ROLE_SCHEMA = { + type: 'string', + source: 'attribute', + attribute: 'role', + selector: '*', +}; + +/** + * Filters registered block settings, extending attributes with role. + * + * @param {Object} settings Original block settings. + * + * @return {Object} Filtered block settings. + */ +export function addAttribute( settings ) { + // Allow blocks to specify their own attribute definition with default values if needed. + if ( settings?.attributes?.role?.type ) { + return settings; + } + if ( hasBlockSupport( settings, 'role' ) ) { + // Gracefully handle if settings.attributes is undefined. + settings.attributes = { + ...settings.attributes, + role: ROLE_SCHEMA, + }; + } + + return settings; +} + +/** + * Override props assigned to save component to inject the role attribute, if block + * supports roles. This is only applied if the block's save result is an + * element and not a markup string. + * + * @param {Object} extraProps Additional props applied to save element. + * @param {Object} blockType Block type. + * @param {Object} attributes Current block attributes. + * + * @return {Object} Filtered props applied to save element. + */ +export function addSaveProps( extraProps, blockType, attributes ) { + if ( hasBlockSupport( blockType, 'role' ) ) { + extraProps.role = attributes.role === '' ? null : attributes.role; + } + + return extraProps; +} + +addFilter( 'blocks.registerBlockType', 'core/role/attribute', addAttribute ); +addFilter( + 'blocks.getSaveContent.extraProps', + 'core/role/save-props', + addSaveProps +); diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 674b0645f50215..5951234d975ba6 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -73,6 +73,7 @@ "position": { "sticky": true }, + "role": true, "typography": { "fontSize": true, "lineHeight": true, diff --git a/schemas/CHANGELOG.md b/schemas/CHANGELOG.md index 51d33d49707c27..443bd48324d031 100644 --- a/schemas/CHANGELOG.md +++ b/schemas/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add new block support for the `role` attribute. ([#47868](https://github.com/WordPress/gutenberg/pull/47868/)) + - Add new properties `settings.typography.fluid` and `settings.typography.fontSizes[n].fluidSize` to theme.json to enable fluid typography ([#39529](https://github.com/WordPress/gutenberg/pull/39529)). diff --git a/schemas/json/block.json b/schemas/json/block.json index 5658361d7aab64..3b0a4dc65d6564 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -474,6 +474,11 @@ "description": "A block may want to disable the ability of being converted into a reusable block. By default all blocks can be converted to a reusable block. If supports reusable is set to false, the option to convert the block into a reusable block will not appear.", "default": true }, + "role": { + "type": "boolean", + "description": "Role attributes let you add semantic meaning to non-semantic HTML elements. This property allows enabling the definition of a role for the block, without exposing a UI field.", + "default": false + }, "lock": { "type": "boolean", "description": "A block may want to disable the ability to toggle the lock state. It can be locked/unlocked by a user from the block 'Options' dropdown by default. To disable this behavior, set lock to false.",