diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-search/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-search/editor.scss
index 224f0ce39e6..d47eb352c8b 100644
--- a/plugins/woocommerce-blocks/assets/js/blocks/product-search/editor.scss
+++ b/plugins/woocommerce-blocks/assets/js/blocks/product-search/editor.scss
@@ -14,3 +14,9 @@
}
}
}
+
+.wc-block-components-actions {
+ .block-editor-warning__actions {
+ margin-top: 0;
+ }
+}
diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-search/index.js b/plugins/woocommerce-blocks/assets/js/blocks/product-search/index.js
deleted file mode 100644
index d230edccf20..00000000000
--- a/plugins/woocommerce-blocks/assets/js/blocks/product-search/index.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { createBlock, registerBlockType } from '@wordpress/blocks';
-import { Icon, search } from '@wordpress/icons';
-/**
- * Internal dependencies
- */
-import './style.scss';
-import './editor.scss';
-import Block from './block.js';
-import edit from './edit.js';
-
-const attributes = {
- /**
- * Whether to show the field label.
- */
- hasLabel: {
- type: 'boolean',
- default: true,
- },
-
- /**
- * Search field label.
- */
- label: {
- type: 'string',
- default: __( 'Search', 'woo-gutenberg-products-block' ),
- },
-
- /**
- * Search field placeholder.
- */
- placeholder: {
- type: 'string',
- default: __( 'Search products…', 'woo-gutenberg-products-block' ),
- },
-
- /**
- * Store the instance ID.
- */
- formId: {
- type: 'string',
- default: '',
- },
-};
-
-registerBlockType( 'woocommerce/product-search', {
- title: __( 'Product Search', 'woo-gutenberg-products-block' ),
- icon: {
- src: (
-
- ),
- },
- category: 'woocommerce',
- keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
- description: __(
- 'A search box to allow customers to search for products by keyword.',
- 'woo-gutenberg-products-block'
- ),
- supports: {
- align: [ 'wide', 'full' ],
- },
- example: {
- attributes: {
- hasLabel: true,
- },
- },
- attributes,
- transforms: {
- from: [
- {
- type: 'block',
- blocks: [ 'core/legacy-widget' ],
- // We can't transform if raw instance isn't shown in the REST API.
- isMatch: ( { idBase, instance } ) =>
- idBase === 'woocommerce_product_search' && !! instance?.raw,
- transform: ( { instance } ) =>
- createBlock( 'woocommerce/product-search', {
- label:
- instance.raw.title === ''
- ? __( 'Search', 'woo-gutenberg-products-block' )
- : instance.raw.title,
- } ),
- },
- ],
- },
- deprecated: [
- {
- attributes,
- save( props ) {
- return (
-
-
-
- );
- },
- },
- ],
- edit,
- save() {
- return null;
- },
-} );
diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-search/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-search/index.tsx
new file mode 100644
index 00000000000..04e5c64d137
--- /dev/null
+++ b/plugins/woocommerce-blocks/assets/js/blocks/product-search/index.tsx
@@ -0,0 +1,206 @@
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+/**
+ * External dependencies
+ */
+import { store as blockEditorStore, Warning } from '@wordpress/block-editor';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+import { Icon, search } from '@wordpress/icons';
+import { getSettingWithCoercion } from '@woocommerce/settings';
+import { isBoolean } from '@woocommerce/types';
+import { Button } from '@wordpress/components';
+import {
+ // @ts-ignore waiting for @types/wordpress__blocks update
+ registerBlockVariation,
+ registerBlockType,
+ createBlock,
+} from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+import './editor.scss';
+import Block from './block.js';
+import Edit from './edit.js';
+
+const isBlockVariationAvailable = getSettingWithCoercion(
+ 'isBlockVariationAvailable',
+ false,
+ isBoolean
+);
+
+const attributes = {
+ /**
+ * Whether to show the field label.
+ */
+ hasLabel: {
+ type: 'boolean',
+ default: true,
+ },
+
+ /**
+ * Search field label.
+ */
+ label: {
+ type: 'string',
+ default: __( 'Search', 'woo-gutenberg-products-block' ),
+ },
+
+ /**
+ * Search field placeholder.
+ */
+ placeholder: {
+ type: 'string',
+ default: __( 'Search products…', 'woo-gutenberg-products-block' ),
+ },
+
+ /**
+ * Store the instance ID.
+ */
+ formId: {
+ type: 'string',
+ default: '',
+ },
+};
+
+const PRODUCT_SEARCH_ATTRIBUTES = {
+ label: attributes.label.default,
+ buttonText: attributes.label.default,
+ placeholder: attributes.placeholder.default,
+ query: {
+ post_type: 'product',
+ },
+};
+
+const DeprecatedBlockEdit = ( { clientId }: { clientId: string } ) => {
+ // @ts-ignore @wordpress/block-editor/store types not provided
+ const { replaceBlocks } = useDispatch( blockEditorStore );
+
+ const currentBlockAttributes = useSelect(
+ ( select ) =>
+ select( 'core/block-editor' ).getBlockAttributes( clientId ),
+ [ clientId ]
+ );
+
+ const updateBlock = () => {
+ replaceBlocks(
+ clientId,
+ createBlock( 'core/search', {
+ label:
+ currentBlockAttributes?.label ||
+ PRODUCT_SEARCH_ATTRIBUTES.label,
+ buttonText: PRODUCT_SEARCH_ATTRIBUTES.buttonText,
+ placeholder:
+ currentBlockAttributes?.placeholder ||
+ PRODUCT_SEARCH_ATTRIBUTES.placeholder,
+ query: PRODUCT_SEARCH_ATTRIBUTES.query,
+ } )
+ );
+ };
+
+ const actions = [
+ ,
+ ];
+
+ return (
+
+ { __(
+ 'This version of the Product Search block is outdated. Upgrade to continue using.',
+ 'woo-gutenberg-products-block'
+ ) }
+
+ );
+};
+
+registerBlockType( 'woocommerce/product-search', {
+ title: __( 'Product Search', 'woo-gutenberg-products-block' ),
+ icon: {
+ src: (
+
+ ),
+ },
+ category: 'woocommerce',
+ keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
+ description: __(
+ 'A search box to allow customers to search for products by keyword.',
+ 'woo-gutenberg-products-block'
+ ),
+ supports: {
+ align: [ 'wide', 'full' ],
+ inserter: ! isBlockVariationAvailable,
+ },
+ example: {
+ attributes: {
+ hasLabel: true,
+ },
+ },
+ attributes,
+ transforms: {
+ from: [
+ {
+ type: 'block',
+ blocks: [ 'core/legacy-widget' ],
+ // We can't transform if raw instance isn't shown in the REST API.
+ isMatch: ( { idBase, instance } ) =>
+ idBase === 'woocommerce_product_search' && !! instance?.raw,
+ transform: ( { instance } ) =>
+ createBlock( 'woocommerce/product-search', {
+ label:
+ instance.raw.title ||
+ PRODUCT_SEARCH_ATTRIBUTES.label,
+ } ),
+ },
+ ],
+ },
+ deprecated: [
+ {
+ attributes,
+ save( props ) {
+ return (
+
+
+
+ );
+ },
+ },
+ ],
+ edit: isBlockVariationAvailable ? DeprecatedBlockEdit : Edit,
+ save() {
+ return null;
+ },
+} );
+
+if ( isBlockVariationAvailable ) {
+ registerBlockVariation( 'core/search', {
+ name: 'woocommerce/product-search',
+ title: __( 'Product Search', 'woo-gutenberg-products-block' ),
+ icon: {
+ src: (
+
+ ),
+ },
+ // @ts-ignore waiting for @types/wordpress__blocks update
+ isActive: ( blockAttributes, variationAttributes ) => {
+ return (
+ blockAttributes.query?.post_type ===
+ variationAttributes.query.post_type
+ );
+ },
+ category: 'woocommerce',
+ keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
+ description: __(
+ 'A search box to allow customers to search for products by keyword.',
+ 'woo-gutenberg-products-block'
+ ),
+ attributes: PRODUCT_SEARCH_ATTRIBUTES,
+ } );
+}
diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductSearch.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductSearch.php
index 613aa67d77d..2afa20ac5ec 100644
--- a/plugins/woocommerce-blocks/src/BlockTypes/ProductSearch.php
+++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductSearch.php
@@ -124,4 +124,36 @@ class ProductSearch extends AbstractBlock {
$label_markup . $field_markup
);
}
+
+ /**
+ * Extra data passed through from server to client for block.
+ *
+ * @param array $attributes Any attributes that currently are available from the block.
+ * Note, this will be empty in the editor context when the block is
+ * not in the post content on editor load.
+ */
+ protected function enqueue_data( array $attributes = [] ) {
+ parent::enqueue_data( $attributes );
+
+ $gutenberg_version = '';
+
+ if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) {
+ if ( defined( 'GUTENBERG_VERSION' ) ) {
+ $gutenberg_version = GUTENBERG_VERSION;
+ }
+
+ if ( ! $gutenberg_version ) {
+ $gutenberg_data = get_file_data(
+ WP_PLUGIN_DIR . '/gutenberg/gutenberg.php',
+ array( 'Version' => 'Version' )
+ );
+ $gutenberg_version = $gutenberg_data['Version'];
+ }
+ }
+
+ $this->asset_data_registry->add(
+ 'isBlockVariationAvailable',
+ version_compare( get_bloginfo( 'version' ), '6.1', '>=' ) || version_compare( $gutenberg_version, '13.4', '>=' )
+ );
+ }
}
diff --git a/plugins/woocommerce-blocks/tests/e2e/specs/backend/product-search.test.js b/plugins/woocommerce-blocks/tests/e2e/specs/backend/product-search.test.js
index b4189ab1810..d7f99c391ff 100644
--- a/plugins/woocommerce-blocks/tests/e2e/specs/backend/product-search.test.js
+++ b/plugins/woocommerce-blocks/tests/e2e/specs/backend/product-search.test.js
@@ -12,52 +12,60 @@ import {
visitBlockPage,
} from '@woocommerce/blocks-test-utils';
+/**
+ * Internal dependencies
+ */
+import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../utils';
+
const block = {
name: 'Product Search',
slug: 'woocommerce/product-search',
class: '.wc-block-product-search',
};
-describe( `${ block.name } Block`, () => {
- beforeAll( async () => {
- await switchUserToAdmin();
- await visitBlockPage( `${ block.name } Block` );
- } );
+describeOrSkip( GUTENBERG_EDITOR_CONTEXT !== 'gutenberg' )(
+ `${ block.name } Block`,
+ () => {
+ beforeAll( async () => {
+ await switchUserToAdmin();
+ await visitBlockPage( `${ block.name } Block` );
+ } );
- it( 'renders without crashing', async () => {
- await expect( page ).toRenderBlock( block );
- } );
+ it( 'renders without crashing', async () => {
+ await expect( page ).toRenderBlock( block );
+ } );
- it( 'can toggle field label', async () => {
- await openDocumentSettingsSidebar();
- await page.click( block.class );
- const selector = `${ block.class } .wc-block-product-search__label`;
- const toggleLabel = await findLabelWithText(
- 'Show search field label'
- );
- await expect( toggleLabel ).toToggleElement( selector );
- } );
+ it( 'can toggle field label', async () => {
+ await openDocumentSettingsSidebar();
+ await page.click( block.class );
+ const selector = `${ block.class } .wc-block-product-search__label`;
+ const toggleLabel = await findLabelWithText(
+ 'Show search field label'
+ );
+ await expect( toggleLabel ).toToggleElement( selector );
+ } );
- it( 'can change field labels in editor', async () => {
- await expect( page ).toFill(
- 'textarea.wc-block-product-search__label',
- 'I am a new label'
- );
+ it( 'can change field labels in editor', async () => {
+ await expect( page ).toFill(
+ 'textarea.wc-block-product-search__label',
+ 'I am a new label'
+ );
- await expect( page ).toFill(
- '.wc-block-product-search__field input',
- 'I am a new placeholder'
- );
+ await expect( page ).toFill(
+ '.wc-block-product-search__field input',
+ 'I am a new placeholder'
+ );
- await clearAndFillInput(
- 'textarea.wc-block-product-search__label',
- 'The Label'
- );
- await clearAndFillInput(
- '.wc-block-product-search__field input',
- 'The Placeholder'
- );
+ await clearAndFillInput(
+ 'textarea.wc-block-product-search__label',
+ 'The Label'
+ );
+ await clearAndFillInput(
+ '.wc-block-product-search__field input',
+ 'The Placeholder'
+ );
- expect( await getEditedPostContent() ).toMatchSnapshot();
- } );
-} );
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
+ }
+);
diff --git a/plugins/woocommerce-blocks/tests/e2e/utils.js b/plugins/woocommerce-blocks/tests/e2e/utils.js
index 3a4f127c19f..b9fe0e20128 100644
--- a/plugins/woocommerce-blocks/tests/e2e/utils.js
+++ b/plugins/woocommerce-blocks/tests/e2e/utils.js
@@ -438,3 +438,18 @@ export const openBlockEditorSettings = async ( { isFSEEditor = false } ) => {
export const waitForAllProductsBlockLoaded = async () => {
await page.waitForSelector( SELECTORS.allProductsBlock.productsList );
};
+
+/**
+ * Execute or skip the test suite base on the provided condition.
+ *
+ * @param {boolean} condition Condition to execute test suite.
+ */
+export const describeOrSkip = ( condition ) =>
+ condition ? describe : describe.skip;
+
+/**
+ * Execute or skip the test base on the provided condition.
+ *
+ * @param {boolean} condition Condition to execute test.
+ */
+export const itOrSkip = ( condition ) => ( condition ? it : it.skip );