diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md
index 1078608345e526..d1de0e98fa12ac 100644
--- a/docs/reference-guides/block-api/block-transforms.md
+++ b/docs/reference-guides/block-api/block-transforms.md
@@ -42,7 +42,7 @@ A transformation of type `block` is an object that takes the following parameter
- **type** _(string)_: the value `block`.
- **blocks** _(array)_: a list of known block types. It also accepts the wildcard value (`"*"`), meaning that the transform is available to _all_ block types (eg: all blocks can transform into `core/group`).
- **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects.
-- **isMatch** _(function, optional)_: a callback that receives the block attributes and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user.
+- **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user.
- **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If true, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default.
- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
diff --git a/lib/blocks.php b/lib/blocks.php
index cc3f4c8662254e..77dcaae989b556 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -106,9 +106,11 @@ function gutenberg_reregister_core_block_types() {
__DIR__ . '/../build/widgets/blocks/' => array(
'block_folders' => array(
'legacy-widget',
+ 'widget-group',
),
'block_names' => array(
'legacy-widget.php' => 'core/legacy-widget',
+ 'widget-group.php' => 'core/widget-group',
),
),
);
diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md
index b51c240a069ea1..486282484503c1 100644
--- a/packages/blocks/CHANGELOG.md
+++ b/packages/blocks/CHANGELOG.md
@@ -6,6 +6,10 @@
- Register a block even when an invalid value provided for the icon setting ([#34350](https://github.com/WordPress/gutenberg/pull/34350)).
+### New API
+
+- The `isMatch` callback on block transforms now receives the block object (or block objects if `isMulti` is `true`) as its second argument.
+
## 11.0.0 (2021-07-29)
### Breaking Change
diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js
index 5b1239c46fc7cf..3013a3d9f79e5c 100644
--- a/packages/blocks/src/api/factory.js
+++ b/packages/blocks/src/api/factory.js
@@ -224,7 +224,8 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => {
const attributes = transform.isMultiBlock
? blocks.map( ( block ) => block.attributes )
: sourceBlock.attributes;
- if ( ! transform.isMatch( attributes ) ) {
+ const block = transform.isMultiBlock ? blocks : sourceBlock;
+ if ( ! transform.isMatch( attributes, block ) ) {
return false;
}
}
diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js
index fc38d753e997b6..3b4d8671f6e1ba 100644
--- a/packages/blocks/src/api/test/factory.js
+++ b/packages/blocks/src/api/test/factory.js
@@ -979,7 +979,7 @@ describe( 'block factory', () => {
expect( availableBlocks ).toEqual( [] );
} );
- it( 'for a non multiblock transform, the isMatch function receives the source block’s attributes object as its first argument', () => {
+ it( 'for a non multiblock transform, the isMatch function receives the source block’s attributes object and the block object as its arguments', () => {
const isMatch = jest.fn();
registerBlockType( 'core/updated-text-block', {
@@ -1010,10 +1010,10 @@ describe( 'block factory', () => {
getPossibleBlockTransformations( [ block ] );
- expect( isMatch ).toHaveBeenCalledWith( { value: 'ribs' } );
+ expect( isMatch ).toHaveBeenCalledWith( { value: 'ribs' }, block );
} );
- it( 'for a multiblock transform, the isMatch function receives an array containing every source block’s attributes as its first argument', () => {
+ it( 'for a multiblock transform, the isMatch function receives an array containing every source block’s attributes and an array of source blocks as its arguments', () => {
const isMatch = jest.fn();
registerBlockType( 'core/updated-text-block', {
@@ -1049,10 +1049,10 @@ describe( 'block factory', () => {
getPossibleBlockTransformations( [ meatBlock, cheeseBlock ] );
- expect( isMatch ).toHaveBeenCalledWith( [
- { value: 'ribs' },
- { value: 'halloumi' },
- ] );
+ expect( isMatch ).toHaveBeenCalledWith(
+ [ { value: 'ribs' }, { value: 'halloumi' } ],
+ [ meatBlock, cheeseBlock ]
+ );
} );
describe( 'wildcard block transforms', () => {
diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js
index 8751caab3a8115..ef93ba1264c8b7 100644
--- a/packages/customize-widgets/src/index.js
+++ b/packages/customize-widgets/src/index.js
@@ -10,6 +10,7 @@ import {
import {
registerLegacyWidgetBlock,
registerLegacyWidgetVariations,
+ registerWidgetGroupBlock,
} from '@wordpress/widgets';
import { setFreeformContentHandlerName } from '@wordpress/blocks';
import { dispatch } from '@wordpress/data';
@@ -56,6 +57,7 @@ export function initialize( editorName, blockEditorSettings ) {
} );
}
registerLegacyWidgetVariations( blockEditorSettings );
+ registerWidgetGroupBlock();
// As we are unregistering `core/freeform` to avoid the Classic block, we must
// replace it with something as the default freeform content handler. Failure to
diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js
index 15961a863047e6..c45bb294ab46fa 100644
--- a/packages/edit-widgets/src/index.js
+++ b/packages/edit-widgets/src/index.js
@@ -16,6 +16,7 @@ import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wor
import {
registerLegacyWidgetBlock,
registerLegacyWidgetVariations,
+ registerWidgetGroupBlock,
} from '@wordpress/widgets';
import { dispatch } from '@wordpress/data';
import { store as interfaceStore } from '@wordpress/interface';
@@ -26,6 +27,7 @@ import { store as interfaceStore } from '@wordpress/interface';
import './store';
import './filters';
import * as widgetArea from './blocks/widget-area';
+
import Layout from './components/layout';
import {
ALLOW_REUSABLE_BLOCKS,
@@ -89,6 +91,8 @@ export function initialize( id, settings ) {
}
registerLegacyWidgetVariations( settings );
registerBlock( widgetArea );
+ registerWidgetGroupBlock();
+
settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) =>
fetchLinkSuggestions( search, searchOptions, settings );
diff --git a/packages/widgets/src/blocks/widget-group/block.json b/packages/widgets/src/blocks/widget-group/block.json
new file mode 100644
index 00000000000000..ec48d90eda5ca7
--- /dev/null
+++ b/packages/widgets/src/blocks/widget-group/block.json
@@ -0,0 +1,18 @@
+{
+ "apiVersion": 2,
+ "name": "core/widget-group",
+ "category": "widgets",
+ "attributes": {
+ "title": {
+ "type": "string"
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": true,
+ "customClassName": true,
+ "reusable": false
+ },
+ "editorStyle": "wp-block-widget-group-editor",
+ "style": "wp-block-widget-group"
+}
diff --git a/packages/widgets/src/blocks/widget-group/edit.js b/packages/widgets/src/blocks/widget-group/edit.js
new file mode 100644
index 00000000000000..663946364904de
--- /dev/null
+++ b/packages/widgets/src/blocks/widget-group/edit.js
@@ -0,0 +1,84 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ useBlockProps,
+ BlockIcon,
+ ButtonBlockAppender,
+ InnerBlocks,
+ store as blockEditorStore,
+} from '@wordpress/block-editor';
+import { Placeholder, TextControl } from '@wordpress/components';
+import { group as groupIcon } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import { useSelect } from '@wordpress/data';
+import { getBlockType } from '@wordpress/blocks';
+
+export default function Edit( props ) {
+ const { clientId, isSelected } = props;
+ const { innerBlocks } = useSelect( ( select ) =>
+ select( blockEditorStore ).getBlock( clientId )
+ );
+
+ let content;
+ if ( innerBlocks.length === 0 ) {
+ content =