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

Add allowedBlocks field to block.json to specify allowed children #58262

Merged
merged 13 commits into from
Jan 29, 2024
Merged
30 changes: 25 additions & 5 deletions docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ registerBlockType( 'gutenberg-examples/example-06', {

## Allowed blocks

Using the `allowedBlocks` property, you can define the set of blocks allowed in your InnerBlock. This restricts the blocks that can be included only to those listed, all other blocks will not show in the inserter.
Using the `allowedBlocks` prop, you can further limit, in addition to the `allowedBlocks` field in `block.json`, which blocks can be inserted as direct descendants of this block. It is useful to determine the list of allowed blocks dynamically, individually for each block. For example, determined by a block attribute:

```js
const ALLOWED_BLOCKS = [ 'core/image', 'core/paragraph' ];
const { allowedBlocks } = attributes;
//...
<InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } />;
<InnerBlocks allowedBlocks={ allowedBlocks } />;
```

If the list of allowed blocks is always the same, prefer the [`allowedBlocks` block setting](#defining-a-children-block-relationship) instead.

## Orientation

By default, `InnerBlocks` expects its blocks to be shown in a vertical list. A valid use-case is to style inner blocks to appear horizontally, for instance by adding CSS flex or grid properties to the inner blocks wrapper. When blocks are styled in such a way, the `orientation` prop can be set to indicate that a horizontal layout is being used:
Expand Down Expand Up @@ -109,12 +111,13 @@ add_action( 'init', function() {
} );
```

## Using parent and ancestor relationships in blocks
## Using parent, ancestor and children relationships in blocks

A common pattern for using InnerBlocks is to create a custom block that will be only be available if its parent block is inserted. This allows builders to establish a relationship between blocks, while limiting a nested block's discoverability. Currently, there are two relationships builders can use: `parent` and `ancestor`. The differences are:
A common pattern for using InnerBlocks is to create a custom block that will only be available if its parent block is inserted. This allows builders to establish a relationship between blocks, while limiting a nested block's discoverability. There are three relationships that builders can use: `parent`, `ancestor` and `allowedBlocks`. The differences are:

- If you assign a `parent` then you’re stating that the nested block can only be used and inserted as a __direct descendant of the parent__.
- If you assign an `ancestor` then you’re stating that the nested block can only be used and inserted as a __descendent of the parent__.
- If you assign the `allowedBlocks` then you’re stating a relationship in the opposite direction, i.e., which blocks can be used and inserted as __direct descendants of this block__.

The key difference between `parent` and `ancestor` is `parent` has finer specificity, while an `ancestor` has greater flexibility in its nested hierarchy.

Expand Down Expand Up @@ -150,6 +153,23 @@ When defining a descendent block, use the `ancestor` block setting. This prevent
}
```

### Defining a children block relationship

An example of this is the Navigation block, which is assigned the `allowedBlocks` block setting. This makes only a certain subset of block types to be available as direct descendants of the Navigation block. See [Navigation code for reference](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/navigation).

The `allowedBlocks` setting can be extended by builders of custom blocks. The custom block can hook into the `blocks.registerBlockType` filter and add itself to the available children of the Navigation.

When defining a set of possible descendant blocks, use the `allowedBlocks` block setting. This limits what blocks are showing in the inserter when inserting a new child block.

```json
{
"title": "Navigation",
"name": "core/navigation",
"allowedBlocks": [ "core/navigation-link", "core/search", "core/social-links", "core/page-list", "core/spacer" ],
// ...
}
```

## Using a React hook

You can use a react hook called `useInnerBlocksProps` instead of the `InnerBlocks` component. This hook allows you to take more control over the markup of inner blocks areas.
Expand Down
14 changes: 14 additions & 0 deletions docs/reference-guides/block-api/block-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,20 @@ Setting `parent` lets a block require that it is only available when nested with

The `ancestor` property makes a block available inside the specified block types at any position of the ancestor block subtree. That allows, for example, to place a ‘Comment Content’ block inside a ‘Column’ block, as long as ‘Column’ is somewhere within a ‘Comment Template’ block. In comparison to the `parent` property blocks that specify their `ancestor` can be placed anywhere in the subtree whilst blocks with a specified `parent` need to be direct children.

### Allowed Blocks

- Type: `string[]`
- Optional
- Localized: No
- Property: `allowedBlocks`
- Since: `WordPress 6.5.0`

```json
{ "allowedBlocks": [ "my-block/product" ] }
```

The `allowedBlocks` specifies which block types can be the direct children of the block. For example, a ‘List’ block can allow only ‘List Item’ blocks as children.

### Icon

- Type: `string`
Expand Down
14 changes: 13 additions & 1 deletion docs/reference-guides/block-api/block-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,19 @@ The `ancestor` property makes a block available inside the specified block types
ancestor: [ 'core/columns' ],
```

#### Block Hooks (optional)
#### allowedBlocks (optional)

- **Type:** `Array`
- **Since**: `WordPress 6.5.0`

Setting the `allowedBlocks` property will limit which block types can be nested as direct children of the block.

```js
// Only allow the Columns block to be nested as direct child of this block
allowedBlocks: [ 'core/columns' ],
```

#### blockHooks (optional)

- **Type:** `Object`
- **Since**: `WordPress 6.4.0`
Expand Down
1 change: 1 addition & 0 deletions lib/compat/wordpress-6.4/block-hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function gutenberg_add_hooked_blocks( $settings, $metadata ) {
'keywords' => 'keywords',
'example' => 'example',
'variations' => 'variations',
'allowed_blocks' => 'allowedBlocks',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ockham I added the allowed_blocks field also here, but I don't really know what I'm doing. Is the field relevant in the block hooks code?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to add here. This is the compatibility layer that implements the Block Hooks "shim" for when the GB plugin runs on WP < 6.4; we're adding the blockHooks field to the preloaded block settings (which unfortunately involves duplicating the $fields_to_pick allowlist from Core, as it's not exposed by any filter or the like).

Since we're now also adding the allowed_blocks field in block.json, it makes sense to include that field in the allowlist 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should update files under 6.4 compat folder. If we intend to use this feature in 6.5, we should have a new file under that folder and I guess it will require a backport. --cc @gziolo @Mamaduka

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have the WordPress Core part as outlined in #58262 (comment), then it definitely might become an issue when support for WordPress 6.3 in the Gutenberg plugin drops and this polyfill is no longer there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nik is right. PHP code should be moved to a target WP version compat directory to avoid missing the backport.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I modified gutenberg_add_hooked_blocks function that's intended to polyfill the block hooks behavior for WP < 6.4. WordPress 6.4 and newer already has the relevant code directly in the register_block_type_from_metadata function and doesn't need any patching.

I'll need to add a separate WordPress 6.5 back compat code in order to support allowedBlocks, both in Core and in the Gutenberg plugin. But I haven't done that yet.

Copy link
Member Author

@jsnajdr jsnajdr Jan 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gziolo @ockham https://core.trac.wordpress.org/ticket/60403 and WordPress/wordpress-develop#5988 should be all we need in Core to support allowedBlocks. I closely followed what Bernie did when adding blockHooks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent, I’ll take it from here and commit tomorrow unless Bernie is faster. Thank you so much @jsnajdr. Stellar job!

);
// Add `block_hooks` to the list of fields to pick.
$fields_to_pick['block_hooks'] = 'blockHooks';
Expand Down
24 changes: 20 additions & 4 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1563,14 +1563,30 @@ const canInsertBlockTypeUnmemoized = (
return false;
}

const parentAllowedBlocks = parentBlockListSettings?.allowedBlocks;
const hasParentAllowedBlock = checkAllowList(
parentAllowedBlocks,
const parentName = getBlockName( state, rootClientId );
const parentBlockType = getBlockType( parentName );

// Look at the `blockType.allowedBlocks` field to determine whether this is an allowed child block.
const parentAllowedChildBlocks = parentBlockType?.allowedBlocks;
let hasParentAllowedBlock = checkAllowList(
parentAllowedChildBlocks,
blockName
);

// The `allowedBlocks` block list setting can further limit which blocks are allowed children.
if ( hasParentAllowedBlock !== false ) {
const parentAllowedBlocks = parentBlockListSettings?.allowedBlocks;
const hasParentListAllowedBlock = checkAllowList(
parentAllowedBlocks,
blockName
);
// Never downgrade the result from `true` to `null`
if ( hasParentListAllowedBlock !== null ) {
hasParentAllowedBlock = hasParentListAllowedBlock;
}
}

const blockAllowedParentBlocks = blockType.parent;
const parentName = getBlockName( state, rootClientId );
const hasBlockAllowedParent = checkAllowList(
blockAllowedParentBlocks,
parentName
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/buttons/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "core/buttons",
"title": "Buttons",
"category": "design",
"allowedBlocks": [ "core/button" ],
"description": "Prompt visitors to take action with a group of button-style links.",
"keywords": [ "link" ],
"textdomain": "default",
Expand Down
17 changes: 4 additions & 13 deletions packages/block-library/src/buttons/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,8 @@ import {
import { useSelect } from '@wordpress/data';
import { store as blocksStore } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { name as buttonBlockName } from '../button';

const ALLOWED_BLOCKS = [ buttonBlockName ];

const DEFAULT_BLOCK = {
name: buttonBlockName,
name: 'core/button',
attributesToCopy: [
'backgroundColor',
'border',
Expand All @@ -48,24 +41,22 @@ function ButtonsEdit( { attributes, className } ) {
select( blockEditorStore ).getSettings()
.__experimentalPreferredStyleVariations;
const buttonVariations = select( blocksStore ).getBlockVariations(
buttonBlockName,
'core/button',
'inserter'
);
return {
preferredStyle:
preferredStyleVariations?.value?.[ buttonBlockName ],
preferredStyle: preferredStyleVariations?.value?.[ 'core/button' ],
hasButtonVariations: buttonVariations.length > 0,
};
}, [] );

const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: ALLOWED_BLOCKS,
defaultBlock: DEFAULT_BLOCK,
// This check should be handled by the `Inserter` internally to be consistent across all blocks that use it.
directInsert: ! hasButtonVariations,
template: [
[
buttonBlockName,
'core/button',
{ className: preferredStyle && `is-style-${ preferredStyle }` },
],
],
Expand Down
8 changes: 2 additions & 6 deletions packages/block-library/src/buttons/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ import { alignmentHelpers } from '@wordpress/components';
/**
* Internal dependencies
*/
import { name as buttonBlockName } from '../button/';
import styles from './editor.scss';

const ALLOWED_BLOCKS = [ buttonBlockName ];

const layoutProp = { type: 'default', alignments: [] };

const POPOVER_PROPS = {
Expand Down Expand Up @@ -76,7 +73,7 @@ export default function ButtonsEdit( {
const preferredStyleVariations =
select( blockEditorStore ).getSettings()
.__experimentalPreferredStyleVariations;
return preferredStyleVariations?.value?.[ buttonBlockName ];
return preferredStyleVariations?.value?.[ 'core/button' ];
}, [] );

const { getBlockOrder } = useSelect( blockEditorStore );
Expand Down Expand Up @@ -147,10 +144,9 @@ export default function ButtonsEdit( {
) }
{ resizeObserver }
<InnerBlocks
allowedBlocks={ ALLOWED_BLOCKS }
template={ [
[
buttonBlockName,
'core/button',
{
className:
preferredStyle &&
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/columns/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "core/columns",
"title": "Columns",
"category": "design",
"allowedBlocks": [ "core/column" ],
"description": "Display content in multiple columns, with blocks added to each column.",
"textdomain": "default",
"attributes": {
Expand Down
12 changes: 0 additions & 12 deletions packages/block-library/src/columns/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,6 @@ import {
toWidthPrecision,
} from './utils';

/**
* Allowed blocks constant is passed to InnerBlocks precisely as specified here.
* The contents of the array should never change.
* The array should contain the name of each block that is allowed.
* In columns block, the only block we allow is 'core/column'.
*
* @constant
* @type {string[]}
*/
const ALLOWED_BLOCKS = [ 'core/column' ];

function ColumnsEditContainer( {
attributes,
setAttributes,
Expand Down Expand Up @@ -103,7 +92,6 @@ function ColumnsEditContainer( {
className: classes,
} );
const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: ALLOWED_BLOCKS,
orientation: 'horizontal',
renderAppender: false,
templateLock,
Expand Down
12 changes: 0 additions & 12 deletions packages/block-library/src/columns/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,6 @@ import {
} from './columnCalculations.native';
import ColumnsPreview from '../column/column-preview';

/**
* Allowed blocks constant is passed to InnerBlocks precisely as specified here.
* The contents of the array should never change.
* The array should contain the name of each block that is allowed.
* In columns block, the only block we allow is 'core/column'.
*
* @constant
* @type {string[]}
*/
const ALLOWED_BLOCKS = [ 'core/column' ];

/**
* Number of columns to assume for template in case the user opts to skip
* template option selection.
Expand Down Expand Up @@ -275,7 +264,6 @@ function ColumnsEditContainer( {
columnsInRow > 1 ? 'horizontal' : undefined
}
horizontal={ columnsInRow > 1 }
allowedBlocks={ ALLOWED_BLOCKS }
contentResizeMode="stretch"
onAddBlock={ onAddBlock }
onDeleteBlock={
Expand Down
5 changes: 5 additions & 0 deletions packages/block-library/src/comments-pagination/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"title": "Comments Pagination",
"category": "theme",
"parent": [ "core/comments" ],
"allowedBlocks": [
"core/comments-pagination-previous",
"core/comments-pagination-numbers",
"core/comments-pagination-next"
],
"description": "Displays a paginated navigation to next/previous set of comments, when applicable.",
"textdomain": "default",
"attributes": {
Expand Down
6 changes: 0 additions & 6 deletions packages/block-library/src/comments-pagination/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ const TEMPLATE = [
[ 'core/comments-pagination-numbers' ],
[ 'core/comments-pagination-next' ],
];
const ALLOWED_BLOCKS = [
'core/comments-pagination-previous',
'core/comments-pagination-numbers',
'core/comments-pagination-next',
];

export default function QueryPaginationEdit( {
attributes: { paginationArrow },
Expand All @@ -52,7 +47,6 @@ export default function QueryPaginationEdit( {
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
template: TEMPLATE,
allowedBlocks: ALLOWED_BLOCKS,
} );

// Get the Discussion settings
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/form-submit-button/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"category": "common",
"icon": "button",
"ancestor": [ "core/form" ],
"allowedBlocks": [ "core/buttons", "core/button" ],
"description": "A submission button for forms.",
"keywords": [ "submit", "button", "form" ],
"textdomain": "default",
Expand Down
1 change: 0 additions & 1 deletion packages/block-library/src/form-submit-button/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const TEMPLATE = [
const Edit = () => {
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: TEMPLATE,
aristath marked this conversation as resolved.
Show resolved Hide resolved
template: TEMPLATE,
templateLock: 'all',
} );
Expand Down
9 changes: 9 additions & 0 deletions packages/block-library/src/form/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
"name": "core/form",
"title": "Form",
"category": "common",
"allowedBlocks": [
"core/paragraph",
"core/heading",
"core/form-input",
"core/form-submit-button",
"core/form-submission-notification",
"core/group",
"core/columns"
],
"description": "A form.",
"keywords": [ "container", "wrapper", "row", "section" ],
"textdomain": "default",
Expand Down
11 changes: 0 additions & 11 deletions packages/block-library/src/form/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,6 @@ import {
formSubmissionNotificationError,
} from './utils.js';

const ALLOWED_BLOCKS = [
'core/paragraph',
'core/heading',
'core/form-input',
'core/form-submit-button',
'core/form-submission-notification',
'core/group',
'core/columns',
];

const TEMPLATE = [
formSubmissionNotificationSuccess,
formSubmissionNotificationError,
Expand Down Expand Up @@ -76,7 +66,6 @@ const Edit = ( { attributes, setAttributes, clientId } ) => {
);

const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: ALLOWED_BLOCKS,
template: TEMPLATE,
renderAppender: hasInnerBlocks
? undefined
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/gallery/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "core/gallery",
"title": "Gallery",
"category": "media",
"allowedBlocks": [ "core/image" ],
"description": "Display multiple images in a rich gallery.",
"keywords": [ "images", "photos" ],
"textdomain": "default",
Expand Down
Loading
Loading