diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js
index 57df36c7c74a0b..0c29c0e98b1bfd 100644
--- a/packages/block-editor/src/components/block-edit/index.js
+++ b/packages/block-editor/src/components/block-edit/index.js
@@ -1,9 +1,9 @@
/**
* WordPress dependencies
*/
-import { useMemo } from '@wordpress/element';
-
+import { useMemo, useContext } from '@wordpress/element';
import { hasBlockSupport } from '@wordpress/blocks';
+
/**
* Internal dependencies
*/
@@ -17,6 +17,8 @@ import {
blockBindingsKey,
isPreviewModeKey,
} from './context';
+import { MultipleUsageWarning } from './multiple-usage-warning';
+import { PrivateBlockContext } from '../block-list/private-block-context';
/**
* The `useBlockEditContext` hook provides information about the block this hook is being used in.
@@ -49,6 +51,8 @@ export default function BlockEdit( {
const layoutSupport =
hasBlockSupport( name, 'layout', false ) ||
hasBlockSupport( name, '__experimentalLayout', false );
+ const { originalBlockClientId } = useContext( PrivateBlockContext );
+
return (
+ { originalBlockClientId && (
+
+ ) }
);
}
diff --git a/packages/block-editor/src/components/block-edit/multiple-usage-warning.js b/packages/block-editor/src/components/block-edit/multiple-usage-warning.js
new file mode 100644
index 00000000000000..4acd4d1f349dd0
--- /dev/null
+++ b/packages/block-editor/src/components/block-edit/multiple-usage-warning.js
@@ -0,0 +1,46 @@
+/**
+ * WordPress dependencies
+ */
+import { getBlockType } from '@wordpress/blocks';
+import { Button } from '@wordpress/components';
+import { useDispatch } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { store as blockEditorStore } from '../../store';
+import Warning from '../warning';
+
+export function MultipleUsageWarning( {
+ originalBlockClientId,
+ name,
+ onReplace,
+} ) {
+ const { selectBlock } = useDispatch( blockEditorStore );
+ const blockType = getBlockType( name );
+
+ return (
+ selectBlock( originalBlockClientId ) }
+ >
+ { __( 'Find original' ) }
+ ,
+ ,
+ ] }
+ >
+ { blockType?.title }:
+ { __( 'This block can only be used once.' ) }
+
+ );
+}
diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js
index cbb1b769b53368..0220a9877eba28 100644
--- a/packages/block-editor/src/components/block-list/block.js
+++ b/packages/block-editor/src/components/block-list/block.js
@@ -23,6 +23,7 @@ import {
isUnmodifiedBlock,
isReusableBlock,
getBlockDefaultClassName,
+ hasBlockSupport,
store as blocksStore,
} from '@wordpress/blocks';
import { withFilters } from '@wordpress/components';
@@ -534,6 +535,7 @@ function BlockListBlockProvider( props ) {
isFirstMultiSelectedBlock,
getMultiSelectedBlockClientIds,
hasSelectedInnerBlock,
+ getBlocksByName,
getBlockIndex,
isBlockMultiSelected,
@@ -607,6 +609,17 @@ function BlockListBlockProvider( props ) {
const movingClientId = hasBlockMovingClientId();
const blockEditingMode = getBlockEditingMode( clientId );
+ const multiple = hasBlockSupport( blockName, 'multiple', true );
+
+ // For block types with `multiple` support, there is no "original
+ // block" to be found in the content, as the block itself is valid.
+ const blocksWithSameName = multiple
+ ? []
+ : getBlocksByName( blockName );
+ const isInvalid =
+ blocksWithSameName.length &&
+ blocksWithSameName[ 0 ] !== clientId;
+
return {
...previewContext,
mode: getBlockMode( clientId ),
@@ -664,6 +677,9 @@ function BlockListBlockProvider( props ) {
hasEditableOutline:
blockEditingMode !== 'disabled' &&
getBlockEditingMode( rootClientId ) === 'disabled',
+ originalBlockClientId: isInvalid
+ ? blocksWithSameName[ 0 ]
+ : false,
};
},
[ clientId, rootClientId ]
@@ -707,6 +723,7 @@ function BlockListBlockProvider( props ) {
hasEditableOutline,
className,
defaultClassName,
+ originalBlockClientId,
} = selectedProps;
// Users of the editor.BlockListBlock filter used to be able to
@@ -754,6 +771,7 @@ function BlockListBlockProvider( props ) {
defaultClassName,
mayDisplayControls,
mayDisplayParentControls,
+ originalBlockClientId,
themeSupportsLayout,
};
diff --git a/packages/edit-post/src/hooks/index.js b/packages/edit-post/src/hooks/index.js
deleted file mode 100644
index 0397f1fee936c3..00000000000000
--- a/packages/edit-post/src/hooks/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Internal dependencies
- */
-import './validate-multiple-use';
diff --git a/packages/edit-post/src/hooks/validate-multiple-use/index.js b/packages/edit-post/src/hooks/validate-multiple-use/index.js
deleted file mode 100644
index bf771f45220362..00000000000000
--- a/packages/edit-post/src/hooks/validate-multiple-use/index.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- createBlock,
- findTransform,
- getBlockTransforms,
- getBlockType,
- hasBlockSupport,
-} from '@wordpress/blocks';
-import { Button } from '@wordpress/components';
-import { withSelect, withDispatch } from '@wordpress/data';
-import { Warning, store as blockEditorStore } from '@wordpress/block-editor';
-import { addFilter } from '@wordpress/hooks';
-import { __ } from '@wordpress/i18n';
-import { compose, createHigherOrderComponent } from '@wordpress/compose';
-
-/**
- * Recursively find very first block of an specific block type.
- *
- * @param {Object[]} blocks List of blocks.
- * @param {string} name Block name to search.
- *
- * @return {Object|undefined} Return block object or undefined.
- */
-function findFirstOfSameType( blocks, name ) {
- if ( ! Array.isArray( blocks ) || ! blocks.length ) {
- return;
- }
-
- for ( const block of blocks ) {
- if ( block.name === name ) {
- return block;
- }
-
- // Search inside innerBlocks.
- const firstBlock = findFirstOfSameType( block.innerBlocks, name );
-
- if ( firstBlock ) {
- return firstBlock;
- }
- }
-}
-
-const enhance = compose(
- /**
- * For blocks whose block type doesn't support `multiple`, provides the
- * wrapped component with `originalBlockClientId` -- a reference to the
- * first block of the same type in the content -- if and only if that
- * "original" block is not the current one. Thus, an inexisting
- * `originalBlockClientId` prop signals that the block is valid.
- *
- * @param {Component} WrappedBlockEdit A filtered BlockEdit instance.
- *
- * @return {Component} Enhanced component with merged state data props.
- */
- withSelect( ( select, block ) => {
- const multiple = hasBlockSupport( block.name, 'multiple', true );
-
- // For block types with `multiple` support, there is no "original
- // block" to be found in the content, as the block itself is valid.
- if ( multiple ) {
- return {};
- }
-
- // Otherwise, only pass `originalBlockClientId` if it refers to a different
- // block from the current one.
- const blocks = select( blockEditorStore ).getBlocks();
- const firstOfSameType = findFirstOfSameType( blocks, block.name );
- const isInvalid =
- firstOfSameType && firstOfSameType.clientId !== block.clientId;
- return {
- originalBlockClientId: isInvalid && firstOfSameType.clientId,
- };
- } ),
- withDispatch( ( dispatch, { originalBlockClientId } ) => ( {
- selectFirst: () =>
- dispatch( blockEditorStore ).selectBlock( originalBlockClientId ),
- } ) )
-);
-
-const withMultipleValidation = createHigherOrderComponent( ( BlockEdit ) => {
- return enhance( ( { originalBlockClientId, selectFirst, ...props } ) => {
- if ( ! originalBlockClientId ) {
- return ;
- }
-
- const blockType = getBlockType( props.name );
- const outboundType = getOutboundType( props.name );
-
- return [
-
-
-
,
-
- { __( 'Find original' ) }
- ,
- ,
- outboundType && (
-
- ),
- ] }
- >
- { blockType?.title }:
- { __( 'This block can only be used once.' ) }
- ,
- ];
- } );
-}, 'withMultipleValidation' );
-
-/**
- * Given a base block name, returns the default block type to which to offer
- * transforms.
- *
- * @param {string} blockName Base block name.
- *
- * @return {?Object} The chosen default block type.
- */
-function getOutboundType( blockName ) {
- // Grab the first outbound transform.
- const transform = findTransform(
- getBlockTransforms( 'to', blockName ),
- ( { type, blocks } ) => type === 'block' && blocks.length === 1 // What about when .length > 1?
- );
-
- if ( ! transform ) {
- return null;
- }
-
- return getBlockType( transform.blocks[ 0 ] );
-}
-
-addFilter(
- 'editor.BlockEdit',
- 'core/edit-post/validate-multiple-use/with-multiple-validation',
- withMultipleValidation
-);
diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js
index 1e0b3fe7d4d6ff..f19248fa0d51c2 100644
--- a/packages/edit-post/src/index.js
+++ b/packages/edit-post/src/index.js
@@ -19,7 +19,6 @@ import { store as editorStore } from '@wordpress/editor';
/**
* Internal dependencies
*/
-import './hooks';
import Editor from './editor';
/**