From 3fb1400a712aa5a2f7a23ca214ac7911cc024668 Mon Sep 17 00:00:00 2001
From: Bernie Reiter <96308+ockham@users.noreply.github.com>
Date: Wed, 6 Sep 2023 16:01:06 +0200
Subject: [PATCH] Rename "auto inserting blocks" to "block hooks" (#54147)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
See https://github.com/WordPress/gutenberg/issues/53987#issuecomment-1695915874:
> I've seen anecdotal feedback that `autoInsert` is not the clearest of descriptions. I'd like to propose renaming to the more familiar `hooks` terminology—and "block hooks" in more general terms—to help folks understand the mechanics and purpose more rapidly.
---
lib/compat/wordpress-6.3/rest-api.php | 4 +-
...o-inserting-blocks.php => block-hooks.php} | 245 ++++++++--------
...tenberg-rest-block-patterns-controller.php | 2 +-
lib/experimental/editor-settings.php | 4 +-
lib/experimental/rest-api.php | 2 +-
lib/experiments-page.php | 8 +-
lib/load.php | 6 +-
.../src/hooks/auto-inserting-blocks.js | 271 ------------------
.../block-editor/src/hooks/block-hooks.js | 259 +++++++++++++++++
...inserting-blocks.scss => block-hooks.scss} | 2 +-
packages/block-editor/src/hooks/index.js | 2 +-
packages/block-editor/src/style.scss | 2 +-
packages/block-library/src/pattern/index.php | 2 +-
packages/blocks/src/api/registration.js | 2 +-
packages/blocks/src/store/reducer.js | 10 +-
15 files changed, 407 insertions(+), 414 deletions(-)
rename lib/experimental/{auto-inserting-blocks.php => block-hooks.php} (74%)
delete mode 100644 packages/block-editor/src/hooks/auto-inserting-blocks.js
create mode 100644 packages/block-editor/src/hooks/block-hooks.js
rename packages/block-editor/src/hooks/{auto-inserting-blocks.scss => block-hooks.scss} (89%)
diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php
index 130da6ed774d8..041398eda24b4 100644
--- a/lib/compat/wordpress-6.3/rest-api.php
+++ b/lib/compat/wordpress-6.3/rest-api.php
@@ -85,9 +85,9 @@ function add_modified_wp_template_schema() {
}
add_filter( 'rest_api_init', 'add_modified_wp_template_schema' );
-// If the Auto-inserting Blocks experiment is enabled, we load the block patterns
+// If the Block Hooks experiment is enabled, we load the block patterns
// controller in lib/experimental/rest-api.php instead.
-if ( ! gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+if ( ! gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) {
/**
* Registers the block patterns REST API routes.
*/
diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/block-hooks.php
similarity index 74%
rename from lib/experimental/auto-inserting-blocks.php
rename to lib/experimental/block-hooks.php
index 4790dc4166fa3..abe85ee3b9ace 100644
--- a/lib/experimental/auto-inserting-blocks.php
+++ b/lib/experimental/block-hooks.php
@@ -1,110 +1,26 @@
0 ) {
- if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) {
- $anchor_block_index--;
- }
- $chunk_index++;
- }
- // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`)
- // when rendering blocks, we also need to insert a value (`null`, to mark a block
- // location) into that array.
- array_splice( $block['innerContent'], $chunk_index, 0, array( null ) );
- }
- return $block;
- };
-}
-
-/**
- * Add auto-insertion information to a block type's controller.
- *
- * @param array $inserted_block_type The type of block to insert.
- * @param string $position The position relative to the anchor block.
- * Can be 'before', 'after', 'first_child', or 'last_child'.
- * @param string $anchor_block_type The auto-inserted block will be inserted next to instances of this block type.
- * @return callable A filter for the `rest_prepare_block_type` hook that adds an `auto_insert` field to the network response.
- */
-function gutenberg_add_auto_insert_field_to_block_type_controller( $inserted_block_type, $position, $anchor_block_type ) {
- return function( $response, $block_type ) use ( $inserted_block_type, $position, $anchor_block_type ) {
- if ( $block_type->name !== $inserted_block_type ) {
- return $response;
- }
-
- $data = $response->get_data();
- if ( ! isset( $data['auto_insert'] ) ) {
- $data['auto_insert'] = array();
- }
- $data['auto_insert'][ $anchor_block_type ] = $position;
- $response->set_data( $data );
- return $response;
- };
-}
-
-/**
- * Register blocks for auto-insertion, based on their block.json metadata.
+ * Register hooked blocks for automatic insertion, based on their block.json metadata.
*
* @param array $settings Array of determined settings for registering a block type.
* @param array $metadata Metadata provided for registering a block type.
* @return array Updated settings array.
*/
-function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) {
- if ( ! isset( $metadata['__experimentalAutoInsert'] ) ) {
+function gutenberg_add_hooked_blocks( $settings, $metadata ) {
+ if ( ! isset( $metadata['__experimentalBlockHooks'] ) ) {
return $settings;
}
- $auto_insert = $metadata['__experimentalAutoInsert'];
+ $block_hooks = $metadata['__experimentalBlockHooks'];
/**
* Map the camelCased position string from block.json to the snake_cased block type position
- * used in the auto-inserting block registration function.
+ * used in the hooked block registration function.
*
* @var array
*/
@@ -116,12 +32,12 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) {
);
$inserted_block_name = $metadata['name'];
- foreach ( $auto_insert as $anchor_block_name => $position ) {
- // Avoid infinite recursion (auto-inserting next to or into self).
+ foreach ( $block_hooks as $anchor_block_name => $position ) {
+ // Avoid infinite recursion (hooking to itself).
if ( $inserted_block_name === $anchor_block_name ) {
_doing_it_wrong(
__METHOD__,
- __( 'Cannot auto-insert block next to itself.', 'gutenberg' ),
+ __( 'Cannot hook block to itself.', 'gutenberg' ),
'6.4.0'
);
continue;
@@ -133,9 +49,9 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) {
$mapped_position = $property_mappings[ $position ];
- gutenberg_register_auto_inserted_block( $inserted_block_name, $mapped_position, $anchor_block_name );
+ gutenberg_add_hooked_block( $inserted_block_name, $mapped_position, $anchor_block_name );
- $settings['auto_insert'][ $anchor_block_name ] = $mapped_position;
+ $settings['block_hooks'][ $anchor_block_name ] = $mapped_position;
}
// Copied from `get_block_editor_server_block_settings()`.
@@ -158,12 +74,12 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) {
'example' => 'example',
'variations' => 'variations',
);
- // Add `auto_insert` to the list of fields to pick.
- $fields_to_pick['auto_insert'] = 'autoInsert';
+ // Add `block_hooks` to the list of fields to pick.
+ $fields_to_pick['block_hooks'] = 'blockHooks';
$exposed_settings = array_intersect_key( $settings, $fields_to_pick );
- // TODO: Make work for blocks registered via direct call to gutenberg_register_auto_inserted_block().
+ // TODO: Make work for blocks registered via direct call to gutenberg_add_hooked_block().
wp_add_inline_script(
'wp-blocks',
'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( array( $inserted_block_name => $exposed_settings ) ) . ');'
@@ -171,49 +87,138 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) {
return $settings;
}
-add_filter( 'block_type_metadata_settings', 'gutenberg_register_auto_inserted_blocks', 10, 2 );
+add_filter( 'block_type_metadata_settings', 'gutenberg_add_hooked_blocks', 10, 2 );
/**
- * Register block for auto-insertion into the frontend and REST API.
+ * Register a hooked block for automatic insertion into a given block hook.
*
- * Register a block for auto-insertion into the frontend and into the markup
+ * A block hook is specified by a block type and a relative position. The hooked block
+ * will be automatically inserted in the given position next to the "anchor" block
+ * whenever the latter is encountered. This applies both to the frontend and to the markup
* returned by the templates and patterns REST API endpoints.
*
- * This is currently done by filtering parsed blocks as obtained from a block template
- * template part, or pattern and injecting the auto-inserted block where applicable.
+ * This is currently done by filtering parsed blocks as obtained from a block template,
+ * template part, or pattern, and injecting the hooked block where applicable.
*
- * @todo In the long run, we'd likely want some sort of registry for auto-inserted blocks.
+ * @todo In the long run, we'd likely want some sort of registry for hooked blocks.
*
- * @param string $inserted_block The name of the block to insert.
- * @param string $position The desired position of the auto-inserted block, relative to its anchor block.
- * Can be 'before', 'after', 'first_child', or 'last_child'.
- * @param string $anchor_block The name of the block to insert the auto-inserted block next to.
+ * @param string $hooked_block The name of the block to insert.
+ * @param string $position The desired position of the hooked block, relative to its anchor block.
+ * Can be 'before', 'after', 'first_child', or 'last_child'.
+ * @param string $anchor_block The name of the block to insert the hooked block next to.
* @return void
*/
-function gutenberg_register_auto_inserted_block( $inserted_block, $position, $anchor_block ) {
- $inserted_block_array = array(
- 'blockName' => $inserted_block,
+function gutenberg_add_hooked_block( $hooked_block, $position, $anchor_block ) {
+ $hooked_block_array = array(
+ 'blockName' => $hooked_block,
'attrs' => array(),
'innerHTML' => '',
'innerContent' => array(),
'innerBlocks' => array(),
);
- $inserter = gutenberg_auto_insert_block( $inserted_block_array, $position, $anchor_block );
+ $inserter = gutenberg_insert_hooked_block( $hooked_block_array, $position, $anchor_block );
add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 );
/*
* The block-types REST API controller uses objects of the `WP_Block_Type` class, which are
* in turn created upon block type registration. However, that class does not contain
- * an `auto_insert` property (and is not easily extensible), so we have to use a different
- * mechanism to communicate to the controller which blocks have been registered for
- * auto-insertion. We're doing so here (i.e. upon block registration), by adding a filter to
+ * a `block_hooks` property (and is not easily extensible), so we have to use a different
+ * mechanism to communicate to the controller which hooked blocks have been registered for
+ * automatic insertion. We're doing so here (i.e. upon block registration), by adding a filter to
* the controller's response.
*/
- $controller_extender = gutenberg_add_auto_insert_field_to_block_type_controller( $inserted_block, $position, $anchor_block );
+ $controller_extender = gutenberg_add_block_hooks_field_to_block_type_controller( $hooked_block, $position, $anchor_block );
add_filter( 'rest_prepare_block_type', $controller_extender, 10, 2 );
}
+/**
+ * Return a function that auto-inserts a block next to a given "anchor" block.
+ *
+ * This is a helper function used in the implementation of block hooks.
+ * It is not meant for public use.
+ *
+ * The auto-inserted block can be inserted before or after the anchor block,
+ * or as the first or last child of the anchor block.
+ *
+ * Note that the returned function mutates the automatically inserted block's
+ * designated parent block by inserting into the parent's `innerBlocks` array,
+ * and by updating the parent's `innerContent` array accordingly.
+ *
+ * @param array $inserted_block The block to insert.
+ * @param string $relative_position The position relative to the given block.
+ * Can be 'before', 'after', 'first_child', or 'last_child'.
+ * @param string $anchor_block_type The automatically inserted block will be inserted next to instances of this block type.
+ * @return callable A function that accepts a block's content and returns the content with the inserted block.
+ */
+function gutenberg_insert_hooked_block( $inserted_block, $relative_position, $anchor_block_type ) {
+ return function( $block ) use ( $inserted_block, $relative_position, $anchor_block_type ) {
+ if ( $anchor_block_type === $block['blockName'] ) {
+ if ( 'first_child' === $relative_position ) {
+ array_unshift( $block['innerBlocks'], $inserted_block );
+ // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`)
+ // when rendering blocks, we also need to prepend a value (`null`, to mark a block
+ // location) to that array.
+ array_unshift( $block['innerContent'], null );
+ } elseif ( 'last_child' === $relative_position ) {
+ array_push( $block['innerBlocks'], $inserted_block );
+ // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`)
+ // when rendering blocks, we also need to prepend a value (`null`, to mark a block
+ // location) to that array.
+ array_push( $block['innerContent'], null );
+ }
+ return $block;
+ }
+
+ $anchor_block_index = array_search( $anchor_block_type, array_column( $block['innerBlocks'], 'blockName' ), true );
+ if ( false !== $anchor_block_index && ( 'after' === $relative_position || 'before' === $relative_position ) ) {
+ if ( 'after' === $relative_position ) {
+ $anchor_block_index++;
+ }
+ array_splice( $block['innerBlocks'], $anchor_block_index, 0, array( $inserted_block ) );
+
+ // Find matching `innerContent` chunk index.
+ $chunk_index = 0;
+ while ( $anchor_block_index > 0 ) {
+ if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) {
+ $anchor_block_index--;
+ }
+ $chunk_index++;
+ }
+ // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`)
+ // when rendering blocks, we also need to insert a value (`null`, to mark a block
+ // location) into that array.
+ array_splice( $block['innerContent'], $chunk_index, 0, array( null ) );
+ }
+ return $block;
+ };
+}
+
+/**
+ * Add block hooks information to a block type's controller.
+ *
+ * @param array $inserted_block_type The type of block to insert.
+ * @param string $position The position relative to the anchor block.
+ * Can be 'before', 'after', 'first_child', or 'last_child'.
+ * @param string $anchor_block_type The hooked block will be inserted next to instances of this block type.
+ * @return callable A filter for the `rest_prepare_block_type` hook that adds a `block_hooks` field to the network response.
+ */
+function gutenberg_add_block_hooks_field_to_block_type_controller( $inserted_block_type, $position, $anchor_block_type ) {
+ return function( $response, $block_type ) use ( $inserted_block_type, $position, $anchor_block_type ) {
+ if ( $block_type->name !== $inserted_block_type ) {
+ return $response;
+ }
+
+ $data = $response->get_data();
+ if ( ! isset( $data['block_hooks'] ) ) {
+ $data['block_hooks'] = array();
+ }
+ $data['block_hooks'][ $anchor_block_type ] = $position;
+ $response->set_data( $data );
+ return $response;
+ };
+}
+
/**
* Parse and reserialize block templates to allow running filters.
*
@@ -325,14 +330,14 @@ function gutenberg_serialize_blocks( $blocks ) {
}
/**
- * Register the `auto_insert` field for the block-types REST API controller.
+ * Register the `block_hooks` field for the block-types REST API controller.
*
* @return void
*/
-function gutenberg_register_auto_insert_rest_field() {
+function gutenberg_register_block_hooks_rest_field() {
register_rest_field(
'block-type',
- 'auto_insert',
+ 'block_hooks',
array(
'schema' => array(
'description' => __( 'This block is automatically inserted near any occurence of the block types used as keys of this map, into a relative position given by the corresponding value.', 'gutenberg' ),
@@ -346,4 +351,4 @@ function gutenberg_register_auto_insert_rest_field() {
)
);
}
-add_action( 'rest_api_init', 'gutenberg_register_auto_insert_rest_field' );
+add_action( 'rest_api_init', 'gutenberg_register_block_hooks_rest_field' );
diff --git a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php
index 1ac567959b146..e4ac5581910e0 100644
--- a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php
+++ b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php
@@ -26,7 +26,7 @@ class Gutenberg_REST_Block_Patterns_Controller extends Gutenberg_REST_Block_Patt
*/
public function prepare_item_for_response( $item, $request ) {
$response = parent::prepare_item_for_response( $item, $request );
- if ( ! gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+ if ( ! gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) {
return $response;
}
diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php
index a9b7b18e75a5f..eb36b5b49b8e9 100644
--- a/lib/experimental/editor-settings.php
+++ b/lib/experimental/editor-settings.php
@@ -31,8 +31,8 @@ function gutenberg_enable_experiments() {
wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' );
}
- if ( $gutenberg_experiments && array_key_exists( 'gutenberg-auto-inserting-blocks', $gutenberg_experiments ) ) {
- wp_add_inline_script( 'wp-block-editor', 'window.__experimentalAutoInsertingBlocks = true', 'before' );
+ if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-hooks', $gutenberg_experiments ) ) {
+ wp_add_inline_script( 'wp-block-editor', 'window.__experimentalBlockHooks = true', 'before' );
}
}
diff --git a/lib/experimental/rest-api.php b/lib/experimental/rest-api.php
index 8e548600b3875..f8c5a3041d312 100644
--- a/lib/experimental/rest-api.php
+++ b/lib/experimental/rest-api.php
@@ -10,7 +10,7 @@
die( 'Silence is golden.' );
}
-if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+if ( gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) {
/**
* Registers the block patterns REST API routes.
*/
diff --git a/lib/experiments-page.php b/lib/experiments-page.php
index 74b2ad01672f7..6a022963807e3 100644
--- a/lib/experiments-page.php
+++ b/lib/experiments-page.php
@@ -104,14 +104,14 @@ function gutenberg_initialize_experiments_settings() {
);
add_settings_field(
- 'gutenberg-auto-inserting-blocks',
- __( 'Auto-inserting blocks', 'gutenberg' ),
+ 'gutenberg-block-hooks',
+ __( 'Block hooks', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
- 'label' => __( 'Test Auto-inserting blocks', 'gutenberg' ),
- 'id' => 'gutenberg-auto-inserting-blocks',
+ 'label' => __( 'Block hooks allow automatically inserting a block in a position relative to another.', 'gutenberg' ),
+ 'id' => 'gutenberg-block-hooks',
)
);
diff --git a/lib/load.php b/lib/load.php
index ef8da334debe6..51a6500396650 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -63,7 +63,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require_once __DIR__ . '/experimental/class-wp-rest-customizer-nonces.php';
}
require_once __DIR__ . '/experimental/class-gutenberg-rest-template-revision-count.php';
- if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+ if ( gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) {
require_once __DIR__ . '/experimental/class-gutenberg-rest-block-patterns-controller.php';
}
require_once __DIR__ . '/experimental/rest-api.php';
@@ -110,8 +110,8 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/disable-tinymce.php';
}
-if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
- require __DIR__ . '/experimental/auto-inserting-blocks.php';
+if ( gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) {
+ require __DIR__ . '/experimental/block-hooks.php';
}
require __DIR__ . '/experimental/interactivity-api/class-wp-interactivity-store.php';
require __DIR__ . '/experimental/interactivity-api/store.php';
diff --git a/packages/block-editor/src/hooks/auto-inserting-blocks.js b/packages/block-editor/src/hooks/auto-inserting-blocks.js
deleted file mode 100644
index 5b3adfbdde8b9..0000000000000
--- a/packages/block-editor/src/hooks/auto-inserting-blocks.js
+++ /dev/null
@@ -1,271 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { addFilter } from '@wordpress/hooks';
-import { Fragment, useMemo } from '@wordpress/element';
-import {
- __experimentalHStack as HStack,
- PanelBody,
- ToggleControl,
-} from '@wordpress/components';
-import { createHigherOrderComponent } from '@wordpress/compose';
-import { createBlock, store as blocksStore } from '@wordpress/blocks';
-import { useDispatch, useSelect } from '@wordpress/data';
-
-/**
- * Internal dependencies
- */
-import { BlockIcon, InspectorControls } from '../components';
-import { store as blockEditorStore } from '../store';
-
-const EMPTY_OBJECT = {};
-
-function AutoInsertingBlocksControl( props ) {
- const blockTypes = useSelect(
- ( select ) => select( blocksStore ).getBlockTypes(),
- []
- );
-
- const autoInsertedBlocksForCurrentBlock = useMemo(
- () =>
- blockTypes?.filter(
- ( { autoInsert } ) =>
- autoInsert && props.blockName in autoInsert
- ),
- [ blockTypes, props.blockName ]
- );
-
- const { blockIndex, rootClientId, innerBlocksLength } = useSelect(
- ( select ) => {
- const { getBlock, getBlockIndex, getBlockRootClientId } =
- select( blockEditorStore );
-
- return {
- blockIndex: getBlockIndex( props.clientId ),
- innerBlocksLength: getBlock( props.clientId )?.innerBlocks
- ?.length,
- rootClientId: getBlockRootClientId( props.clientId ),
- };
- },
- [ props.clientId ]
- );
-
- const autoInsertedBlockClientIds = useSelect(
- ( select ) => {
- const { getBlock, getGlobalBlockCount } =
- select( blockEditorStore );
-
- const _autoInsertedBlockClientIds =
- autoInsertedBlocksForCurrentBlock.reduce(
- ( clientIds, block ) => {
- // If the block doesn't exist anywhere in the block tree,
- // we know that we have to display the toggle for it, and set
- // it to disabled.
- if ( getGlobalBlockCount( block.name ) === 0 ) {
- return clientIds;
- }
-
- const relativePosition =
- block?.autoInsert?.[ props.blockName ];
- let candidates;
-
- switch ( relativePosition ) {
- case 'before':
- case 'after':
- // Any of the current block's siblings (with the right block type) qualifies
- // as an auto-inserted block (inserted `before` or `after` the current one),
- // as the block might've been auto-inserted and then moved around a bit by the user.
- candidates =
- getBlock( rootClientId )?.innerBlocks;
- break;
-
- case 'first_child':
- case 'last_child':
- // Any of the current block's child blocks (with the right block type) qualifies
- // as an auto-inserted first or last child block, as the block might've been
- // auto-inserted and then moved around a bit by the user.
- candidates = getBlock(
- props.clientId
- ).innerBlocks;
- break;
- }
-
- const autoInsertedBlock = candidates?.find(
- ( { name } ) => name === block.name
- );
-
- // If the block exists in the designated location, we consider it auto-inserted
- // and show the toggle as enabled.
- if ( autoInsertedBlock ) {
- return {
- ...clientIds,
- [ block.name ]: autoInsertedBlock.clientId,
- };
- }
-
- // If no auto-inserted block was found in any of its designated locations,
- // but it exists elsewhere in the block tree, we consider it manually inserted.
- // In this case, we take note and will remove the corresponding toggle from the
- // block inspector panel.
- return {
- ...clientIds,
- [ block.name ]: false,
- };
- },
- {}
- );
-
- if ( Object.values( _autoInsertedBlockClientIds ).length > 0 ) {
- return _autoInsertedBlockClientIds;
- }
-
- return EMPTY_OBJECT;
- },
- [
- autoInsertedBlocksForCurrentBlock,
- props.blockName,
- props.clientId,
- rootClientId,
- ]
- );
-
- const { insertBlock, removeBlock } = useDispatch( blockEditorStore );
-
- // Remove toggle if block isn't present in the designated location but elsewhere in the block tree.
- const autoInsertedBlocksForCurrentBlockIfNotPresentElsewhere =
- autoInsertedBlocksForCurrentBlock?.filter(
- ( block ) => autoInsertedBlockClientIds?.[ block.name ] !== false
- );
-
- if ( ! autoInsertedBlocksForCurrentBlockIfNotPresentElsewhere.length ) {
- return null;
- }
-
- // Group by block namespace (i.e. prefix before the slash).
- const groupedAutoInsertedBlocks = autoInsertedBlocksForCurrentBlock.reduce(
- ( groups, block ) => {
- const [ namespace ] = block.name.split( '/' );
- if ( ! groups[ namespace ] ) {
- groups[ namespace ] = [];
- }
- groups[ namespace ].push( block );
- return groups;
- },
- {}
- );
-
- const insertBlockIntoDesignatedLocation = ( block, relativePosition ) => {
- switch ( relativePosition ) {
- case 'before':
- case 'after':
- insertBlock(
- block,
- relativePosition === 'after' ? blockIndex + 1 : blockIndex,
- rootClientId, // Insert as a child of the current block's parent
- false
- );
- break;
-
- case 'first_child':
- case 'last_child':
- insertBlock(
- block,
- // TODO: It'd be great if insertBlock() would accept negative indices for insertion.
- relativePosition === 'first_child' ? 0 : innerBlocksLength,
- props.clientId, // Insert as a child of the current block.
- false
- );
- break;
- }
- };
-
- return (
-
-
- { Object.keys( groupedAutoInsertedBlocks ).map( ( vendor ) => {
- return (
-
- { vendor }
- { groupedAutoInsertedBlocks[ vendor ].map(
- ( block ) => {
- const checked =
- block.name in
- autoInsertedBlockClientIds;
-
- return (
-
-
- { block.title }
-
- }
- onChange={ () => {
- if ( ! checked ) {
- // Create and insert block.
- const relativePosition =
- block.autoInsert[
- props.blockName
- ];
- insertBlockIntoDesignatedLocation(
- createBlock(
- block.name
- ),
- relativePosition
- );
- return;
- }
-
- // Remove block.
- const clientId =
- autoInsertedBlockClientIds[
- block.name
- ];
- removeBlock( clientId, false );
- } }
- />
- );
- }
- ) }
-
- );
- } ) }
-
-
- );
-}
-
-export const withAutoInsertingBlocks = createHigherOrderComponent(
- ( BlockEdit ) => {
- return ( props ) => {
- const blockEdit = ;
- return (
- <>
- { blockEdit }
-
- >
- );
- };
- },
- 'withAutoInsertingBlocks'
-);
-
-if ( window?.__experimentalAutoInsertingBlocks ) {
- addFilter(
- 'editor.BlockEdit',
- 'core/auto-inserting-blocks/with-inspector-control',
- withAutoInsertingBlocks
- );
-}
diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js
new file mode 100644
index 0000000000000..678f8ee06d971
--- /dev/null
+++ b/packages/block-editor/src/hooks/block-hooks.js
@@ -0,0 +1,259 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { addFilter } from '@wordpress/hooks';
+import { Fragment, useMemo } from '@wordpress/element';
+import {
+ __experimentalHStack as HStack,
+ PanelBody,
+ ToggleControl,
+} from '@wordpress/components';
+import { createHigherOrderComponent } from '@wordpress/compose';
+import { createBlock, store as blocksStore } from '@wordpress/blocks';
+import { useDispatch, useSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { BlockIcon, InspectorControls } from '../components';
+import { store as blockEditorStore } from '../store';
+
+const EMPTY_OBJECT = {};
+
+function BlockHooksControl( props ) {
+ const blockTypes = useSelect(
+ ( select ) => select( blocksStore ).getBlockTypes(),
+ []
+ );
+
+ const hookedBlocksForCurrentBlock = useMemo(
+ () =>
+ blockTypes?.filter(
+ ( { blockHooks } ) =>
+ blockHooks && props.blockName in blockHooks
+ ),
+ [ blockTypes, props.blockName ]
+ );
+
+ const { blockIndex, rootClientId, innerBlocksLength } = useSelect(
+ ( select ) => {
+ const { getBlock, getBlockIndex, getBlockRootClientId } =
+ select( blockEditorStore );
+
+ return {
+ blockIndex: getBlockIndex( props.clientId ),
+ innerBlocksLength: getBlock( props.clientId )?.innerBlocks
+ ?.length,
+ rootClientId: getBlockRootClientId( props.clientId ),
+ };
+ },
+ [ props.clientId ]
+ );
+
+ const hookedBlockClientIds = useSelect(
+ ( select ) => {
+ const { getBlock, getGlobalBlockCount } =
+ select( blockEditorStore );
+
+ const _hookedBlockClientIds = hookedBlocksForCurrentBlock.reduce(
+ ( clientIds, block ) => {
+ // If the block doesn't exist anywhere in the block tree,
+ // we know that we have to display the toggle for it, and set
+ // it to disabled.
+ if ( getGlobalBlockCount( block.name ) === 0 ) {
+ return clientIds;
+ }
+
+ const relativePosition =
+ block?.blockHooks?.[ props.blockName ];
+ let candidates;
+
+ switch ( relativePosition ) {
+ case 'before':
+ case 'after':
+ // Any of the current block's siblings (with the right block type) qualifies
+ // as a hooked block (inserted `before` or `after` the current one), as the block
+ // might've been automatically inserted and then moved around a bit by the user.
+ candidates = getBlock( rootClientId )?.innerBlocks;
+ break;
+
+ case 'first_child':
+ case 'last_child':
+ // Any of the current block's child blocks (with the right block type) qualifies
+ // as a hooked first or last child block, as the block might've been automatically
+ // inserted and then moved around a bit by the user.
+ candidates = getBlock( props.clientId ).innerBlocks;
+ break;
+ }
+
+ const hookedBlock = candidates?.find(
+ ( { name } ) => name === block.name
+ );
+
+ // If the block exists in the designated location, we consider it hooked
+ // and show the toggle as enabled.
+ if ( hookedBlock ) {
+ return {
+ ...clientIds,
+ [ block.name ]: hookedBlock.clientId,
+ };
+ }
+
+ // If no hooked block was found in any of its designated locations,
+ // but it exists elsewhere in the block tree, we consider it manually inserted.
+ // In this case, we take note and will remove the corresponding toggle from the
+ // block inspector panel.
+ return {
+ ...clientIds,
+ [ block.name ]: false,
+ };
+ },
+ {}
+ );
+
+ if ( Object.values( _hookedBlockClientIds ).length > 0 ) {
+ return _hookedBlockClientIds;
+ }
+
+ return EMPTY_OBJECT;
+ },
+ [
+ hookedBlocksForCurrentBlock,
+ props.blockName,
+ props.clientId,
+ rootClientId,
+ ]
+ );
+
+ const { insertBlock, removeBlock } = useDispatch( blockEditorStore );
+
+ // Remove toggle if block isn't present in the designated location but elsewhere in the block tree.
+ const hookedBlocksForCurrentBlockIfNotPresentElsewhere =
+ hookedBlocksForCurrentBlock?.filter(
+ ( block ) => hookedBlockClientIds?.[ block.name ] !== false
+ );
+
+ if ( ! hookedBlocksForCurrentBlockIfNotPresentElsewhere.length ) {
+ return null;
+ }
+
+ // Group by block namespace (i.e. prefix before the slash).
+ const groupedHookedBlocks = hookedBlocksForCurrentBlock.reduce(
+ ( groups, block ) => {
+ const [ namespace ] = block.name.split( '/' );
+ if ( ! groups[ namespace ] ) {
+ groups[ namespace ] = [];
+ }
+ groups[ namespace ].push( block );
+ return groups;
+ },
+ {}
+ );
+
+ const insertBlockIntoDesignatedLocation = ( block, relativePosition ) => {
+ switch ( relativePosition ) {
+ case 'before':
+ case 'after':
+ insertBlock(
+ block,
+ relativePosition === 'after' ? blockIndex + 1 : blockIndex,
+ rootClientId, // Insert as a child of the current block's parent
+ false
+ );
+ break;
+
+ case 'first_child':
+ case 'last_child':
+ insertBlock(
+ block,
+ // TODO: It'd be great if insertBlock() would accept negative indices for insertion.
+ relativePosition === 'first_child' ? 0 : innerBlocksLength,
+ props.clientId, // Insert as a child of the current block.
+ false
+ );
+ break;
+ }
+ };
+
+ return (
+
+
+ { Object.keys( groupedHookedBlocks ).map( ( vendor ) => {
+ return (
+
+ { vendor }
+ { groupedHookedBlocks[ vendor ].map( ( block ) => {
+ const checked =
+ block.name in hookedBlockClientIds;
+
+ return (
+
+
+ { block.title }
+
+ }
+ onChange={ () => {
+ if ( ! checked ) {
+ // Create and insert block.
+ const relativePosition =
+ block.blockHooks[
+ props.blockName
+ ];
+ insertBlockIntoDesignatedLocation(
+ createBlock( block.name ),
+ relativePosition
+ );
+ return;
+ }
+
+ // Remove block.
+ const clientId =
+ hookedBlockClientIds[
+ block.name
+ ];
+ removeBlock( clientId, false );
+ } }
+ />
+ );
+ } ) }
+
+ );
+ } ) }
+
+
+ );
+}
+
+export const withBlockHooks = createHigherOrderComponent( ( BlockEdit ) => {
+ return ( props ) => {
+ const blockEdit = ;
+ return (
+ <>
+ { blockEdit }
+
+ >
+ );
+ };
+}, 'withBlockHooks' );
+
+if ( window?.__experimentalBlockHooks ) {
+ addFilter(
+ 'editor.BlockEdit',
+ 'core/block-hooks/with-inspector-control',
+ withBlockHooks
+ );
+}
diff --git a/packages/block-editor/src/hooks/auto-inserting-blocks.scss b/packages/block-editor/src/hooks/block-hooks.scss
similarity index 89%
rename from packages/block-editor/src/hooks/auto-inserting-blocks.scss
rename to packages/block-editor/src/hooks/block-hooks.scss
index 8c43c3673053e..dc05d6e6947bb 100644
--- a/packages/block-editor/src/hooks/auto-inserting-blocks.scss
+++ b/packages/block-editor/src/hooks/block-hooks.scss
@@ -1,4 +1,4 @@
-.block-editor-hooks__auto-inserting-blocks {
+.block-editor-hooks__block-hooks {
/**
* Since we're displaying the block icon alongside the block name,
* we need to right-align the toggle.
diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js
index 9907fbed89c52..0f6d52cc2c00d 100644
--- a/packages/block-editor/src/hooks/index.js
+++ b/packages/block-editor/src/hooks/index.js
@@ -22,7 +22,7 @@ import './metadata';
import './metadata-name';
import './behaviors';
import './custom-fields';
-import './auto-inserting-blocks';
+import './block-hooks';
import './block-rename-ui';
export { useCustomSides } from './dimensions';
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index e3e65469b4c54..cdb4bb2a07abf 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -46,7 +46,7 @@
@import "./components/url-input/style.scss";
@import "./components/url-popover/style.scss";
@import "./hooks/anchor.scss";
-@import "./hooks/auto-inserting-blocks.scss";
+@import "./hooks/block-hooks.scss";
@import "./hooks/border.scss";
@import "./hooks/color.scss";
@import "./hooks/dimensions.scss";
diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php
index 97a8c3ddc663f..50687220992e2 100644
--- a/packages/block-library/src/pattern/index.php
+++ b/packages/block-library/src/pattern/index.php
@@ -44,7 +44,7 @@ function render_block_core_pattern( $attributes ) {
$content = _inject_theme_attribute_in_block_template_content( $pattern['content'] );
$gutenberg_experiments = get_option( 'gutenberg-experiments' );
- if ( $gutenberg_experiments && ! empty( $gutenberg_experiments['gutenberg-auto-inserting-blocks'] ) ) {
+ if ( $gutenberg_experiments && ! empty( $gutenberg_experiments['gutenberg-block-hooks'] ) ) {
// TODO: In the long run, we'd likely want to have a filter in the `WP_Block_Patterns_Registry` class
// instead to allow us plugging in code like this.
$blocks = parse_blocks( $content );
diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js
index 72c0a30db0205..44776ed7e4992 100644
--- a/packages/blocks/src/api/registration.js
+++ b/packages/blocks/src/api/registration.js
@@ -167,7 +167,7 @@ function getBlockSettingsFromMetadata( { textdomain, ...metadata } ) {
'styles',
'example',
'variations',
- '__experimentalAutoInsert',
+ '__experimentalBlockHooks',
];
const settings = Object.fromEntries(
diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js
index a8f114fea79c7..db01f06bb7db6 100644
--- a/packages/blocks/src/store/reducer.js
+++ b/packages/blocks/src/store/reducer.js
@@ -79,18 +79,18 @@ function bootstrappedBlockTypes( state = {}, action ) {
};
}
- // The `autoInsert` prop is not yet included in the server provided
+ // The `blockHooks` prop is not yet included in the server provided
// definitions and needs to be polyfilled. This can be removed when the
// minimum supported WordPress is >= 6.4.
if (
- serverDefinition.__experimentalAutoInsert === undefined &&
- blockType.__experimentalAutoInsert
+ serverDefinition.__experimentalBlockHooks === undefined &&
+ blockType.__experimentalBlockHooks
) {
newDefinition = {
...serverDefinition,
...newDefinition,
- __experimentalAutoInsert:
- blockType.__experimentalAutoInsert,
+ __experimentalBlockHooks:
+ blockType.__experimentalBlockHooks,
};
}
} else {