[Experimental] Product Filter - Reintroduce Wrapper Block (#43688)

This commit is contained in:
Sam Seay 2024-01-23 13:29:24 +08:00 committed by GitHub
parent 9b7d9ce689
commit 42e77d4938
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 347 additions and 377 deletions

View File

@ -8,7 +8,7 @@ import { isExperimentalBuild } from '@woocommerce/block-settings';
* Internal dependencies
*/
import { EditProps } from './types';
import Upgrade from '../product-filters/components/upgrade';
import Upgrade from '../product-filter/components/upgrade';
const Edit = ( { attributes, clientId }: EditProps ) => {
const blockProps = useBlockProps();

View File

@ -1,7 +1,7 @@
{
"name": "woocommerce/product-filters",
"name": "woocommerce/product-filter",
"version": "1.0.0",
"title": "Product Filters",
"title": "Product Filter",
"description": "A block that adds product filters to the product collection.",
"category": "woocommerce",
"keywords": [ "WooCommerce", "Filters" ],
@ -15,12 +15,15 @@
"collectionData": "collectionData"
},
"attributes": {
"filterType": {
"type": "string"
},
"heading": {
"type": "string"
},
"collectionData": {
"type": "object",
"default": {}
},
"filterType": {
"type": "string"
}
},
"apiVersion": 2,

View File

@ -10,9 +10,15 @@ import { createBlock, BlockInstance } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { UPGRADE_MAP } from './upgrade';
import { FilterType } from '../types';
const Downgrade = ( { clientId }: { clientId: string } ) => {
const Downgrade = ( {
clientId,
filterType,
}: {
clientId: string;
filterType: FilterType;
} ) => {
const { replaceBlock } = useDispatch( 'core/block-editor' );
const block = useSelect( ( select ) => {
return select( 'core/block-editor' ).getBlock( clientId );
@ -20,29 +26,32 @@ const Downgrade = ( { clientId }: { clientId: string } ) => {
const downgradeBlock = () => {
if ( ! block ) return;
const filterBlock = block.innerBlocks[ 0 ];
const filterType = Object.entries( UPGRADE_MAP ).find(
( [ , value ] ) => value === filterBlock.name
)?.[ 0 ];
if ( ! filterType ) return;
const filterBlock = block.innerBlocks.find( ( item ) =>
item.name.includes( 'filter' )
);
const innerBlocks: BlockInstance[] = [
createBlock( `woocommerce/${ filterType }`, {
...filterBlock.attributes,
heading: '',
} ),
];
const headingBlock = filterBlock.innerBlocks.find(
if ( ! filterBlock ) return;
const headingBlock = block.innerBlocks.find(
( item ) => item.name === 'core/heading'
);
const innerBlocks: BlockInstance[] = [];
if ( headingBlock ) {
innerBlocks.unshift(
innerBlocks.push(
createBlock( 'core/heading', headingBlock.attributes )
);
}
innerBlocks.push(
createBlock( `woocommerce/${ filterType }`, {
...filterBlock.attributes,
heading: '',
} )
);
replaceBlock(
clientId,
createBlock(
@ -53,8 +62,6 @@ const Downgrade = ( { clientId }: { clientId: string } ) => {
);
};
if ( block?.innerBlocks.length !== 1 ) return null;
return (
<InspectorControls key="inspector">
<PanelBody title={ __( 'Legacy Block', 'woocommerce' ) }>

View File

@ -19,15 +19,6 @@ import {
*/
import { FilterType } from '../types';
export const UPGRADE_MAP: Record< FilterType, string > = {
'active-filters': 'woocommerce/product-filters-active',
'price-filter': 'woocommerce/product-filters-price',
'stock-filter': 'woocommerce/product-filters-stock-status',
'rating-filter': 'woocommerce/product-filters-rating',
'product-filters': 'woocommerce/product-filters',
'attribute-filter': 'woocommerce/product-filters-attribute',
};
const Upgrade = ( { clientId }: { clientId: string } ) => {
const block = useSelect( ( select ) => {
return select( 'core/block-editor' ).getBlock( clientId );
@ -49,20 +40,11 @@ const Upgrade = ( { clientId }: { clientId: string } ) => {
replaceBlock(
clientId,
createBlock( `woocommerce/product-filters`, {}, [
createBlock(
`${ UPGRADE_MAP[ filterType ] }`,
filterBlockAttributes,
headingBlock
? [
createBlock(
'core/heading',
headingBlock.attributes
),
]
: []
),
] )
createBlock( 'woocommerce/product-filter', {
...filterBlockAttributes,
heading: headingBlock?.attributes.content || '',
filterType,
} )
);
}, [ block, clientId, replaceBlock ] );

View File

@ -0,0 +1,74 @@
/**
* External dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import Downgrade from './components/downgrade';
import Warning from './components/warning';
import './editor.scss';
import { getAllowedBlocks } from './utils';
const BLOCK_NAME_MAP = {
'active-filters': 'woocommerce/product-filter-active',
'price-filter': 'woocommerce/product-filter-price',
'stock-filter': 'woocommerce/product-filter-stock-status',
'rating-filter': 'woocommerce/product-filter-rating',
'attribute-filter': 'woocommerce/product-filter-attribute',
};
type FilterType = keyof typeof BLOCK_NAME_MAP;
const Edit = ( {
attributes,
clientId,
}: BlockEditProps< { heading: string; filterType: FilterType } > ) => {
const blockProps = useBlockProps();
const isNested = useSelect( ( select ) => {
const { getBlockParentsByBlockName } = select( 'core/block-editor' );
return !! getBlockParentsByBlockName(
clientId,
'woocommerce/product-collection'
).length;
} );
return (
<nav { ...blockProps }>
{ ! isNested && <Warning /> }
<Downgrade
filterType={ attributes.filterType }
clientId={ clientId }
/>
<InnerBlocks
allowedBlocks={ getAllowedBlocks( [
...Object.values( BLOCK_NAME_MAP ),
'woocommerce/product-filter',
'woocommerce/filter-wrapper',
'woocommerce/product-collection',
'core/query',
] ) }
template={ [
[
'core/heading',
{ level: 3, content: attributes.heading || '' },
],
[
BLOCK_NAME_MAP[ attributes.filterType ],
{
lock: {
remove: true,
},
},
],
] }
/>
</nav>
);
};
export default Edit;

View File

@ -31,35 +31,18 @@ if ( isExperimentalBuild() ) {
/>
),
},
edit,
save,
variations: [
{
name: 'woocommerce/product-filters-wrapper',
title: __( 'Product Filters', 'woocommerce' ),
description: __(
'Enable customers to filter the product collection.',
'woocommerce'
),
attributes: {
filterType: 'product-filters',
},
icon: {
src: (
<Icon
icon={ more }
className="wc-block-editor-components-block-icon"
/>
),
},
isDefault: true,
},
{
name: 'woocommerce/product-filters-active-wrapper',
title: __( 'Product Filters: Active Filters', 'woocommerce' ),
name: 'product-filter-active',
title: __( 'Product Filter: Active Filters', 'woocommerce' ),
description: __(
'Display the currently active filters.',
'woocommerce'
),
attributes: {
heading: __( 'Active filters', 'woocommerce' ),
filterType: 'active-filters',
},
icon: {
@ -70,17 +53,18 @@ if ( isExperimentalBuild() ) {
/>
),
},
isDefault: true,
},
{
name: 'woocommerce/product-filters-price-wrapper',
title: __( 'Product Filters: Price', 'woocommerce' ),
name: 'product-filter-price',
title: __( 'Product Filter: Price', 'woocommerce' ),
description: __(
'Enable customers to filter the product collection by choosing a price range.',
'woocommerce'
),
attributes: {
filterType: 'price-filter',
heading: __( 'Filter by price', 'woocommerce' ),
heading: __( 'Filter by Price', 'woocommerce' ),
},
icon: {
src: (
@ -92,14 +76,15 @@ if ( isExperimentalBuild() ) {
},
},
{
name: 'woocommerce/product-filters-stock-status-wrapper',
title: __( 'Product Filters: Stock Status', 'woocommerce' ),
name: 'product-filter-stock-status',
title: __( 'Product Filter: Stock Status', 'woocommerce' ),
description: __(
'Enable customers to filter the product collection by stock status.',
'woocommerce'
),
attributes: {
filterType: 'stock-filter',
heading: __( 'Filter by Stock Status', 'woocommerce' ),
},
icon: {
src: (
@ -111,14 +96,15 @@ if ( isExperimentalBuild() ) {
},
},
{
name: 'woocommerce/product-filters-attribute-wrapper',
title: __( 'Product Filters: Attribute', 'woocommerce' ),
name: 'product-filter-attribute',
title: __( 'Product Filter: Attribute', 'woocommerce' ),
description: __(
'Enable customers to filter the product collection by selecting one or more attributes, such as color.',
'woocommerce'
),
attributes: {
filterType: 'attribute-filter',
heading: __( 'Filter by Attribute', 'woocommerce' ),
},
icon: {
src: (
@ -130,14 +116,15 @@ if ( isExperimentalBuild() ) {
},
},
{
name: 'woocommerce/product-filters-rating-wrapper',
title: __( 'Product Filters: Rating', 'woocommerce' ),
name: 'product-filter-rating',
title: __( 'Product Filter: Rating', 'woocommerce' ),
description: __(
'Enable customers to filter the product collection by rating.',
'woocommerce'
),
attributes: {
filterType: 'rating-filter',
heading: __( 'Filter by Rating', 'woocommerce' ),
},
icon: {
src: (
@ -149,7 +136,5 @@ if ( isExperimentalBuild() ) {
},
},
],
edit,
save,
} );
}

View File

@ -1,16 +1,17 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"name": "woocommerce/product-filters-active",
"name": "woocommerce/product-filter-active",
"version": "1.0.0",
"title": "Product Filters: Active Filters",
"title": "Product Filter: Active Filters",
"description": "Display the currently active filters.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"textdomain": "woocommerce",
"apiVersion": 2,
"ancestor": [ "woocommerce/product-filters" ],
"ancestor": [ "woocommerce/product-filter" ],
"supports": {
"interactivity": true,
"inserter": false,
"color": {
"text": true,
"background": false

View File

@ -1,11 +1,10 @@
/**
* External dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import classNames from 'classnames';
import { Disabled } from '@wordpress/components';
import { Template } from '@wordpress/blocks';
/**
* Internal dependencies
@ -21,20 +20,9 @@ const Edit = ( props: EditProps ) => {
className: 'wc-block-active-filters',
} );
const template: Template[] = [
[
'core/heading',
{ content: __( 'Active Filters', 'woocommerce' ), level: 3 },
],
];
return (
<div { ...blockProps }>
<Inspector { ...props } />
<InnerBlocks
template={ template }
allowedBlocks={ [ 'core/heading' ] }
/>
<Disabled>
<ul
className={ classNames( 'wc-block-active-filters__list', {

View File

@ -13,7 +13,7 @@ type ActiveFiltersContext = {
params: string[];
};
store( 'woocommerce/product-filters-active', {
store( 'woocommerce/product-filter-active', {
actions: {
clearAll: () => {
const { params } = getContext< ActiveFiltersContext >();

View File

@ -5,7 +5,6 @@ import { registerBlockType } from '@wordpress/blocks';
import { Icon } from '@wordpress/icons';
import { toggle } from '@woocommerce/icons';
import { isExperimentalBuild } from '@woocommerce/block-settings';
import { InnerBlocks } from '@wordpress/block-editor';
/**
* Internal dependencies
@ -25,6 +24,5 @@ if ( isExperimentalBuild() ) {
),
},
edit: Edit,
save: InnerBlocks.Content,
} );
}

View File

@ -1,16 +1,17 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"name": "woocommerce/product-filters-attribute",
"name": "woocommerce/product-filter-attribute",
"version": "1.0.0",
"title": "Product Filters: Attribute",
"title": "Product Filter: Attribute",
"description": "Enable customers to filter the product grid by selecting one or more attributes, such as color.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"textdomain": "woocommerce",
"apiVersion": 2,
"ancestor": [ "woocommerce/product-filters" ],
"ancestor": [ "woocommerce/product-filter" ],
"supports": {
"interactivity": true,
"inserter": false,
"color": {
"text": true,
"background": false

View File

@ -1,13 +1,9 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import {
BlockControls,
InnerBlocks,
useBlockProps,
} from '@wordpress/block-editor';
import { BlockControls, useBlockProps } from '@wordpress/block-editor';
import { getSetting } from '@woocommerce/settings';
import {
useCollection,
@ -25,7 +21,6 @@ import {
withSpokenMessages,
Notice,
} from '@wordpress/components';
import { Template } from '@wordpress/blocks';
/**
* Internal dependencies
@ -88,22 +83,6 @@ const Edit = ( props: EditProps ) => {
const blockProps = useBlockProps();
const template: Template[] = [
[
'core/heading',
{
content: attributeObject
? sprintf(
// translators: %s is the attribute label.
__( 'Filter by %s', 'woocommerce' ),
attributeObject.label
)
: __( 'Filter Products by Attribute', 'woocommerce' ),
level: 3,
},
],
];
useEffect( () => {
if ( ! attributeObject?.taxonomy ) {
return;
@ -194,7 +173,7 @@ const Edit = ( props: EditProps ) => {
</AttributesPlaceholder>
);
const Wrapper = ( { children }: { children: ReactNode } ) => (
const Wrapper = ( { children }: { children: React.ReactNode } ) => (
<div { ...blockProps }>
<Toolbar />
{ children }
@ -247,10 +226,6 @@ const Edit = ( props: EditProps ) => {
return (
<Wrapper>
<Inspector { ...props } />
<InnerBlocks
template={ template }
allowedBlocks={ [ 'core/heading' ] }
/>
<Disabled>
{ displayStyle === 'dropdown' ? (
<AttributeDropdown

View File

@ -50,7 +50,7 @@ function getSelectedTermsFromUrl( slug: string ) {
.filter( Boolean );
}
store( 'woocommerce/product-filters-attribute', {
store( 'woocommerce/product-filter-attribute', {
actions: {
navigate: () => {
const dropdownContext = getContext< DropdownContext >(

View File

@ -3,7 +3,6 @@
*/
import { registerBlockType } from '@wordpress/blocks';
import { isExperimentalBuild } from '@woocommerce/block-settings';
import { InnerBlocks } from '@wordpress/block-editor';
/**
* Internal dependencies
@ -15,6 +14,5 @@ import Edit from './edit';
if ( isExperimentalBuild() ) {
registerBlockType( metadata, {
edit: Edit,
save: InnerBlocks.Content,
} );
}

View File

@ -1,4 +1,4 @@
.wp-block-woocommerce-product-filters-attribute {
.wp-block-woocommerce-product-filter-attribute {
.style-dropdown {
position: relative;

View File

@ -1,16 +1,17 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"name": "woocommerce/product-filters-price",
"name": "woocommerce/product-filter-price",
"version": "1.0.0",
"title": "Product Filters: Price",
"title": "Product Filter: Price",
"description": "Enable customers to filter the product collection by choosing a price range.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"textdomain": "woocommerce",
"apiVersion": 2,
"ancestor": [ "woocommerce/product-filters" ],
"ancestor": [ "woocommerce/product-filter" ],
"supports": {
"interactivity": true
"interactivity": true,
"inserter": false
},
"usesContext": [ "collectionData" ],
"attributes": {

View File

@ -1,10 +1,8 @@
/**
* External dependencies
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import { useBlockProps } from '@wordpress/block-editor';
import classNames from 'classnames';
import { __ } from '@wordpress/i18n';
import { Template } from '@wordpress/blocks';
/**
* Internal dependencies
@ -22,20 +20,9 @@ const Edit = ( props: EditProps ) => {
} ),
} );
const template: Template[] = [
[
'core/heading',
{ content: __( 'Filter by Price', 'woocommerce' ), level: 3 },
],
];
return (
<div { ...blockProps }>
<Inspector { ...props } />
<InnerBlocks
template={ template }
allowedBlocks={ [ 'core/heading' ] }
/>
<PriceSlider { ...props } />
</div>
);

View File

@ -35,7 +35,7 @@ const getUrl = ( context: PriceFilterContext ) => {
return url.href;
};
store< PriceFilterStore >( 'woocommerce/product-filters-price', {
store< PriceFilterStore >( 'woocommerce/product-filter-price', {
state: {
rangeStyle: () => {
const { minPrice, maxPrice, minRange, maxRange } =

View File

@ -4,7 +4,6 @@
import { registerBlockType } from '@wordpress/blocks';
import { Icon, currencyDollar } from '@wordpress/icons';
import { isExperimentalBuild } from '@woocommerce/block-settings';
import { InnerBlocks } from '@wordpress/block-editor';
/**
* Internal dependencies
@ -24,6 +23,5 @@ if ( isExperimentalBuild() ) {
),
},
edit: Edit,
save: InnerBlocks.Content,
} );
}

View File

@ -26,7 +26,6 @@
border-color: $white;
}
@mixin track {
cursor: default;
height: 1px;
@ -51,15 +50,19 @@
appearance: none;
}
.wp-block-woocommerce-product-filters-price {
.wp-block-woocommerce-product-filter-price {
.range {
--low: 0%;
--high: 100%;
--range-color: currentColor;
--track-background: linear-gradient(to right, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0) no-repeat 0 100% / 100% 100%;
--track-background:
linear-gradient(to right, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0)
no-repeat 0 100% / 100% 100%;
.rtl & {
--track-background: linear-gradient(to left, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0) no-repeat 0 100% / 100% 100%;
--track-background:
linear-gradient(to left, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0)
no-repeat 0 100% / 100% 100%;
}
@include reset;

View File

@ -1,18 +1,19 @@
{
"name": "woocommerce/product-filters-rating",
"name": "woocommerce/product-filter-rating",
"version": "1.0.0",
"title": "Product Filters: Rating",
"title": "Product Filter: Rating",
"description": "Enable customers to filter the product collection by rating.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"supports": {
"interactivity": true,
"inserter": false,
"color": {
"background": false,
"text": true
}
},
"ancestor": [ "woocommerce/product-filters" ],
"ancestor": [ "woocommerce/product-filter" ],
"usesContext": [ "collectionData" ],
"attributes": {
"className": {

View File

@ -3,8 +3,8 @@
*/
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import type { BlockEditProps, Template } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import type { BlockEditProps } from '@wordpress/blocks';
import Rating from '@woocommerce/base-components/product-rating';
import {
useQueryStateByKey,
@ -48,13 +48,6 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
className: classnames( 'wc-block-rating-filter', className ),
} );
const template: Template[] = [
[
'core/heading',
{ content: __( 'Filter by Rating', 'woocommerce' ), level: 3 },
],
];
const isEditor = true;
const setWrapperVisibility = useSetWraperVisibility();
@ -170,10 +163,6 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
<>
<Inspector { ...props } />
<div { ...blockProps }>
<InnerBlocks
template={ template }
allowedBlocks={ [ 'core/heading' ] }
/>
<Disabled>
{ displayNoProductRatingsNotice && <NoRatings /> }
<div

View File

@ -25,7 +25,7 @@ function getUrl( filters: Array< string | null > ) {
return url.href;
}
store( 'woocommerce/product-filters-rating', {
store( 'woocommerce/product-filter-rating', {
actions: {
onCheckboxChange: () => {
const checkboxContext = getContext< CheckboxListContext >(

View File

@ -3,7 +3,6 @@
*/
import { registerBlockType } from '@wordpress/blocks';
import { Icon, starEmpty } from '@wordpress/icons';
import { InnerBlocks } from '@wordpress/block-editor';
/**
* Internal dependencies
@ -24,5 +23,4 @@ registerBlockType( metadata, {
...metadata.attributes,
},
edit,
save: InnerBlocks.Content,
} );

View File

@ -1,7 +1,7 @@
{
"name": "woocommerce/product-filters-stock-status",
"name": "woocommerce/product-filter-stock-status",
"version": "1.0.0",
"title": "Product Filters: Stock Status",
"title": "Product Filter: Stock Status",
"description": "Enable customers to filter the product collection by stock status.",
"category": "woocommerce",
"keywords": [ "WooCommerce", "filter", "stock" ],
@ -9,6 +9,7 @@
"interactivity": true,
"html": false,
"multiple": false,
"inserter": false,
"color": {
"text": true,
"background": false

View File

@ -3,12 +3,12 @@
*/
import { useMemo } from '@wordpress/element';
import classnames from 'classnames';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import { useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { CheckboxList } from '@woocommerce/blocks-components';
import Label from '@woocommerce/base-components/filter-element-label';
import type { BlockEditProps, Template } from '@wordpress/blocks';
import type { BlockEditProps } from '@wordpress/blocks';
import { getSetting } from '@woocommerce/settings';
import { useCollectionData } from '@woocommerce/base-context/hooks';
@ -36,16 +36,6 @@ const Edit = ( props: BlockEditProps< BlockProps > ) => {
),
} );
const template: Template[] = [
[
'core/heading',
{
content: __( 'Filter by Stock Status', 'woocommerce' ),
level: 3,
},
],
];
const { showCounts, displayStyle } = props.attributes;
const stockStatusOptions: Record< string, string > = getSetting(
'stockStatusOptions',
@ -88,10 +78,6 @@ const Edit = ( props: BlockEditProps< BlockProps > ) => {
{
<div { ...blockProps }>
<Inspector { ...props } />
<InnerBlocks
template={ template }
allowedBlocks={ [ 'core/heading' ] }
/>
<Disabled>
<div
className={ classnames(

View File

@ -24,7 +24,7 @@ const getUrl = ( activeFilters: string ) => {
return url.href;
};
store( 'woocommerce/product-filters-stock-status', {
store( 'woocommerce/product-filter-stock-status', {
actions: {
onCheckboxChange: () => {
const checkboxContext = getContext< CheckboxListContext >(

View File

@ -4,7 +4,6 @@
import { registerBlockType } from '@wordpress/blocks';
import { Icon, box } from '@wordpress/icons';
import { isExperimentalBuild } from '@woocommerce/block-settings';
import { InnerBlocks } from '@wordpress/block-editor';
/**
* Internal dependencies
@ -24,6 +23,5 @@ if ( isExperimentalBuild() ) {
),
},
edit,
save: InnerBlocks.Content,
} );
}

View File

@ -9,7 +9,7 @@ export type FilterType =
| 'rating-filter'
| 'active-filters'
| 'stock-filter'
| 'product-filters';
| 'product-filter';
export type BlockAttributes = {
filterType: FilterType;

View File

@ -1,82 +0,0 @@
/**
* External dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { Template } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { getSetting } from '@woocommerce/settings';
import type { AttributeSetting } from '@woocommerce/types';
/**
* Internal dependencies
*/
import { EditProps, FilterType } from './types';
import { getAllowedBlocks } from './utils';
import Downgrade from './components/downgrade';
import Warning from './components/warning';
import './editor.scss';
const DISALLOWED_BLOCKS = [
'woocommerce/filter-wrapper',
'woocommerce/product-filters',
];
const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] );
const firstAttribute = ATTRIBUTES.find( Boolean );
const templates: Partial< Record< FilterType, Template[] > > = {
'active-filters': [ [ 'woocommerce/product-filters-active', {} ] ],
'price-filter': [ [ 'woocommerce/product-filters-price', {} ] ],
'stock-filter': [ [ 'woocommerce/product-filters-stock-status', {} ] ],
'rating-filter': [ [ 'woocommerce/product-filters-rating', {} ] ],
'product-filters': [
[ 'woocommerce/product-filters-active', {} ],
[ 'woocommerce/product-filters-price', {} ],
[ 'woocommerce/product-filters-stock-status', {} ],
[ 'woocommerce/product-filters-rating', {} ],
],
};
if ( firstAttribute ) {
templates[ 'attribute-filter' ] = [
[
'woocommerce/product-filters-attribute',
{ attributeId: parseInt( firstAttribute?.attribute_id, 10 ) },
],
];
templates[ 'product-filters' ]?.push( [
'woocommerce/product-filters-attribute',
{ attributeId: parseInt( firstAttribute?.attribute_id, 10 ) },
] );
}
const Edit = ( props: EditProps ) => {
const allowedBlocks = getAllowedBlocks( DISALLOWED_BLOCKS );
const template = templates[ props.attributes.filterType ];
const blockProps = useBlockProps();
const isNested = useSelect( ( select ) => {
const { getBlockParentsByBlockName } = select( 'core/block-editor' );
return !! getBlockParentsByBlockName(
props.clientId,
'woocommerce/product-collection'
).length;
} );
return (
<nav { ...blockProps }>
{ ! isNested && <Warning /> }
<Downgrade clientId={ props.clientId } />
<InnerBlocks
template={ template }
allowedBlocks={ allowedBlocks }
/>
</nav>
);
};
export default Edit;

View File

@ -88,27 +88,27 @@ const blocks = {
},
'single-product': {},
'stock-filter': {},
'product-filters': {
'product-filter': {
isExperimental: true,
},
'product-filters-stock-status': {
'product-filter-stock-status': {
isExperimental: true,
customDir: 'product-filters/inner-blocks/stock-filter',
customDir: 'product-filter/inner-blocks/stock-filter',
},
'product-filters-price': {
customDir: 'product-filters/inner-blocks/price-filter',
'product-filter-price': {
customDir: 'product-filter/inner-blocks/price-filter',
isExperimental: true,
},
'product-filters-attribute': {
customDir: 'product-filters/inner-blocks/attribute-filter',
'product-filter-attribute': {
customDir: 'product-filter/inner-blocks/attribute-filter',
isExperimental: true,
},
'product-filters-rating': {
customDir: 'product-filters/inner-blocks/rating-filter',
'product-filter-rating': {
customDir: 'product-filter/inner-blocks/rating-filter',
isExperimental: true,
},
'product-filters-active': {
customDir: 'product-filters/inner-blocks/active-filters',
'product-filter-active': {
customDir: 'product-filter/inner-blocks/active-filters',
isExperimental: true,
},
'order-confirmation-summary': {

View File

@ -3,39 +3,30 @@
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
const wrapperBlock = {
name: 'woocommerce/product-filters',
title: 'Product Filters',
};
const filterBlocks = [
{
name: 'woocommerce/product-filters-price',
title: 'Product Filters: Price',
variation: 'Product Filters: Price',
name: 'woocommerce/product-filter-price',
title: 'Product Filter: Price',
heading: 'Filter by Price',
},
{
name: 'woocommerce/product-filters-stock-status',
title: 'Product Filters: Stock Status',
variation: 'Product Filters: Stock Status',
name: 'woocommerce/product-filter-stock-status',
title: 'Product Filter: Stock Status',
heading: 'Filter by Stock Status',
},
{
name: 'woocommerce/product-filters-rating',
title: 'Product Filters: Rating',
variation: 'Product Filters: Rating',
name: 'woocommerce/product-filter-rating',
title: 'Product Filter: Rating',
heading: 'Filter by Rating',
},
{
name: 'woocommerce/product-filters-attribute',
title: 'Product Filters: Attribute',
variation: 'Product Filters: Attribute',
heading: 'Filter by ', // The attribute filter comes with a dynamic title
name: 'woocommerce/product-filter-attribute',
title: 'Product Filter: Attribute',
heading: 'Filter by Attribute',
},
{
name: 'woocommerce/product-filters-active',
title: 'Product Filters: Active Filters',
variation: 'Product Filters: Active Filters',
name: 'woocommerce/product-filter-active',
title: 'Product Filter: Active Filters',
heading: 'Active Filters',
},
];
@ -45,25 +36,13 @@ test.describe( 'Filter blocks registration', async () => {
await admin.createNewPost();
} );
test( 'Wrapper block can be inserted through the inserter', async ( {
test( 'Variations can be inserted through the inserter.', async ( {
editor,
editorUtils,
} ) => {
await editorUtils.insertBlockUsingGlobalInserter( wrapperBlock.title );
await expect(
editor.canvas.getByLabel( `Block: ${ wrapperBlock.title }`, {
exact: true,
} )
).toBeVisible();
} );
test( 'Wrapper block contains all filter blocks by default', async ( {
editor,
editorUtils,
} ) => {
await editorUtils.insertBlockUsingGlobalInserter( wrapperBlock.title );
for ( const block of filterBlocks ) {
await editorUtils.insertBlockUsingGlobalInserter( block.title );
await expect(
editor.canvas.getByLabel( `Block: ${ block.title }` )
).toBeVisible();
@ -74,27 +53,15 @@ test.describe( 'Filter blocks registration', async () => {
editor,
editorUtils,
} ) => {
await editorUtils.insertBlockUsingGlobalInserter( wrapperBlock.title );
for ( const block of filterBlocks ) {
await editorUtils.insertBlockUsingGlobalInserter( block.title );
await expect(
editor.canvas
.getByLabel( `Block: ${ block.title }` )
.getByLabel( `Block: Product Filter` )
.getByLabel( 'Block: Heading' )
.and( editor.canvas.getByText( block.heading ) )
).toBeVisible();
}
} );
test( 'Variations can be inserted through the inserter.', async ( {
editor,
editorUtils,
} ) => {
for ( const block of filterBlocks ) {
await editorUtils.insertBlockUsingGlobalInserter( block.variation );
await expect(
editor.canvas.getByLabel( `Block: ${ block.title }` )
).toBeVisible();
}
} );
} );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
[Experimental] Reintroduce a wrapper block for the interactivity powered filter blocks.

View File

@ -2,7 +2,7 @@
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* FilledCartBlock class.
* FilterWrapper class.
*/
class FilterWrapper extends AbstractBlock {
/**

View File

@ -5,15 +5,15 @@ use Automattic\WooCommerce\Blocks\QueryFilters;
use Automattic\WooCommerce\Blocks\Package;
/**
* Product Filters Block.
* Product Filter Block.
*/
final class ProductFilters extends AbstractBlock {
final class ProductFilter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-filters';
protected $block_name = 'product-filter';
/**
* Cache the current response from the API.
@ -70,6 +70,55 @@ final class ProductFilters extends AbstractBlock {
$this->asset_data_registry->add( 'isWidgetEditor', 'widgets.php' === $pagenow || 'customize.php' === $pagenow, true );
}
/**
* Check if the collection data is empty.
*
* @param mixed $attributes - Block attributes.
* @return bool - Whether the collection data is empty.
*/
private function collection_data_is_empty( $attributes ) {
$filter_type = $attributes['filterType'];
if ( 'active-filters' !== $filter_type && empty( $this->current_response ) ) {
return true;
}
if ( 'attribute-filter' === $filter_type ) {
return empty( $this->current_response['attribute_counts'] );
}
if ( 'rating-filter' === $filter_type ) {
return empty( $this->current_response['rating_counts'] );
}
if ( 'price-filter' === $filter_type ) {
return empty( $this->current_response['price_range'] ) || ( $this->current_response['price_range']['min_price'] === $this->current_response['price_range']['max_price'] );
}
if ( 'stock-filter' === $filter_type ) {
return empty( $this->current_response['stock_status_counts'] );
}
if ( 'active-filters' === $filter_type ) {
// Duplicate query param logic from ProductFilterActive block, to determine if we should
// display the ProductFilter block or not.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
$parsed_url = wp_parse_url( esc_url_raw( $request_uri ) );
$url_query_params = [];
if ( isset( $parsed_url['query'] ) ) {
parse_str( $parsed_url['query'], $url_query_params );
}
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
return empty( array_unique( apply_filters( 'collection_filter_query_param_keys', array(), array_keys( $url_query_params ) ) ) );
}
return false;
}
/**
* Render the block.
*
@ -83,22 +132,82 @@ final class ProductFilters extends AbstractBlock {
return $content;
}
if ( $this->collection_data_is_empty( $attributes ) ) {
return $this->render_empty_block( $block );
}
return $this->render_filter_block( $content, $block );
}
/**
* Reset the current response, must be done before rendering.
*
* @return void
*/
private function reset_current_response() {
/**
* At this point, WP starts rendering the Collection Filters block,
* When WP starts rendering the Product Filters block,
* we can safely unset the current response.
*/
$this->current_response = null;
}
$attributes_data = array(
/**
* Render the block when it's empty.
*
* @param mixed $block - Block instance.
* @return string - Rendered block type output.
*/
private function render_empty_block( $block ) {
$this->reset_current_response();
$attributes = array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'class' => 'wc-block-collection-filters',
'class' => 'wc-block-product-filters',
);
if ( ! isset( $block->context['queryId'] ) ) {
$attributes_data['data-wc-navigation-id'] = sprintf(
'wc-collection-filters-%s',
md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
);
$attributes['data-wc-navigation-id'] = $this->generate_navigation_id( $block );
}
return sprintf(
'<nav %1$s></nav>',
get_block_wrapper_attributes(
$attributes
)
);
}
/**
* Generate a unique navigation ID for the block.
*
* @param mixed $block - Block instance.
* @return string - Unique navigation ID.
*/
private function generate_navigation_id( $block ) {
return sprintf(
'wc-product-filter-%s',
md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
);
}
/**
* Render the block when it's not empty.
*
* @param string $content - Block content.
* @param WP_Block $block - Block instance.
* @return string - Rendered block type output.
*/
private function render_filter_block( $content, $block ) {
$this->reset_current_response();
$attributes_data = array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'class' => 'wc-block-product-filters',
);
if ( ! isset( $block->context['queryId'] ) ) {
$attributes_data['data-wc-navigation-id'] = $this->generate_navigation_id( $block );
}
return sprintf(
@ -122,7 +231,7 @@ final class ProductFilters extends AbstractBlock {
}
/**
* When the first direct child of Collection Filters is rendering, we
* When the first direct child of Product Filters is rendering, we
* hydrate and cache the collection data response.
*/
if (

View File

@ -2,15 +2,15 @@
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* Product Filters: Active Block.
* Product Filter: Active Block.
*/
final class ProductFiltersActive extends AbstractBlock {
final class ProductFilterActive extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-filters-active';
protected $block_name = 'product-filter-active';
/**
* Render the block.
@ -66,7 +66,6 @@ final class ProductFiltersActive extends AbstractBlock {
<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php if ( ! empty( $active_filters ) ) : ?>
<?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<ul class="wc-block-active-filters__list %3$s">
<?php foreach ( $active_filters as $filter ) : ?>
<li>

View File

@ -5,16 +5,16 @@ use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
use Automattic\WooCommerce\Blocks\InteractivityComponents\CheckboxList;
/**
* Product Filters: Attribute Block.
* Product Filter: Attribute Block.
*/
final class ProductFiltersAttribute extends AbstractBlock {
final class ProductFilterAttribute extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-filters-attribute';
protected $block_name = 'product-filter-attribute';
/**
* Initialize this block type.
@ -154,7 +154,6 @@ final class ProductFiltersAttribute extends AbstractBlock {
)
),
);
}
$attribute_terms = get_terms(

View File

@ -2,16 +2,16 @@
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* Product Filters: Price Block.
* Product Filter: Price Block.
*/
final class ProductFiltersPrice extends AbstractBlock {
final class ProductFilterPrice extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-filters-price';
protected $block_name = 'product-filter-price';
const MIN_PRICE_QUERY_VAR = 'min_price';
const MAX_PRICE_QUERY_VAR = 'max_price';

View File

@ -5,17 +5,17 @@ use Automattic\WooCommerce\Blocks\InteractivityComponents\CheckboxList;
use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
/**
* Product Filters: Rating Block
* Product Filter: Rating Block
*
* @package Automattic\WooCommerce\Blocks\BlockTypes
*/
final class ProductFiltersRating extends AbstractBlock {
final class ProductFilterRating extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-filters-rating';
protected $block_name = 'product-filter-rating';
const RATING_FILTER_QUERY_VAR = 'rating_filter';

View File

@ -5,16 +5,16 @@ use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
use Automattic\WooCommerce\Blocks\InteractivityComponents\CheckboxList;
/**
* Product Filters: Stock Status Block.
* Product Filter: Stock Status Block.
*/
final class ProductFiltersStockStatus extends AbstractBlock {
final class ProductFilterStockStatus extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-filters-stock-status';
protected $block_name = 'product-filter-stock-status';
const STOCK_STATUS_QUERY_VAR = 'filter_stock_status';

View File

@ -298,12 +298,12 @@ final class BlockTypesController {
);
if ( Package::feature()->is_experimental_build() ) {
$block_types[] = 'ProductFilters';
$block_types[] = 'ProductFiltersStockStatus';
$block_types[] = 'ProductFiltersPrice';
$block_types[] = 'ProductFiltersAttribute';
$block_types[] = 'ProductFiltersRating';
$block_types[] = 'ProductFiltersActive';
$block_types[] = 'ProductFilter';
$block_types[] = 'ProductFilterStockStatus';
$block_types[] = 'ProductFilterPrice';
$block_types[] = 'ProductFilterAttribute';
$block_types[] = 'ProductFilterRating';
$block_types[] = 'ProductFilterActive';
}
/**