[Experimental] Attribute Filter: Update Inspector Control Settings (#49751)
* add: new inspector controls * update: cleanup the edit component * chore: remove unused old inspector component * tweak: add formatting to help text as Jarek said: > Here’s the copy. Please mind the formatting. * add: default attribute id that closest to 30 * update: pass the whole attribute object * update: use default attribute term in Product Filters template * add: style settings * add: sorting and show empty behavior in the editor preview * tweak: use hideEmpty to align with existing settings * chore: changelog * test: add basic e2e tests * chore: restore previous default value * add: attribute help text and border support * chore: lint * fix: add missing context
This commit is contained in:
parent
6000a1e83c
commit
234f5d7e49
|
@ -14,7 +14,45 @@
|
|||
"inserter": false,
|
||||
"color": {
|
||||
"text": true,
|
||||
"background": false
|
||||
"background": false,
|
||||
"__experimentalDefaultControls": {
|
||||
"text": false
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"fontSize": true,
|
||||
"lineHeight": true,
|
||||
"__experimentalFontWeight": true,
|
||||
"__experimentalFontFamily": true,
|
||||
"__experimentalFontStyle": true,
|
||||
"__experimentalTextTransform": true,
|
||||
"__experimentalTextDecoration": true,
|
||||
"__experimentalLetterSpacing": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"fontSize": false
|
||||
}
|
||||
},
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true,
|
||||
"blockGap": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"margin": false,
|
||||
"padding": false,
|
||||
"blockGap": false
|
||||
}
|
||||
},
|
||||
"__experimentalBorder": {
|
||||
"color": true,
|
||||
"radius": true,
|
||||
"style": true,
|
||||
"width": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"color": false,
|
||||
"radius": false,
|
||||
"style": false,
|
||||
"width": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"usesContext": [ "query", "queryId" ],
|
||||
|
@ -42,6 +80,18 @@
|
|||
"isPreview": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"sortOrder": {
|
||||
"type": "string",
|
||||
"default": "count-desc"
|
||||
},
|
||||
"hideEmpty": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"clearButton": {
|
||||
"type": "boolean",
|
||||
"default":true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import {
|
||||
PanelBody,
|
||||
ToggleControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { AttributeSelectControls } from './attribute-select-controls';
|
||||
import { EditProps } from '../types';
|
||||
|
||||
export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
|
||||
const { attributeId, showCounts, queryType, displayStyle, selectType } =
|
||||
attributes;
|
||||
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody title={ __( 'Display Settings', 'woocommerce' ) }>
|
||||
<ToggleControl
|
||||
label={ __( 'Display product count', 'woocommerce' ) }
|
||||
checked={ showCounts }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showCounts: ! showCounts,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleGroupControl
|
||||
label={ __(
|
||||
'Allow selecting multiple options?',
|
||||
'woocommerce'
|
||||
) }
|
||||
value={ selectType || 'multiple' }
|
||||
onChange={ ( value: string ) =>
|
||||
setAttributes( {
|
||||
selectType: value,
|
||||
} )
|
||||
}
|
||||
className="wc-block-attribute-filter__multiple-toggle"
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value="multiple"
|
||||
label={ _x(
|
||||
'Multiple',
|
||||
'Number of filters',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value="single"
|
||||
label={ _x(
|
||||
'Single',
|
||||
'Number of filters',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
{ selectType === 'multiple' && (
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Filter Conditions', 'woocommerce' ) }
|
||||
help={
|
||||
queryType === 'and'
|
||||
? __(
|
||||
'Choose to return filter results for all of the attributes selected.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Choose to return filter results for any of the attributes selected.',
|
||||
'woocommerce'
|
||||
)
|
||||
}
|
||||
value={ queryType }
|
||||
onChange={ ( value: string ) =>
|
||||
setAttributes( {
|
||||
queryType: value,
|
||||
} )
|
||||
}
|
||||
className="wc-block-attribute-filter__conditions-toggle"
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value="and"
|
||||
label={ __( 'All', 'woocommerce' ) }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value="or"
|
||||
label={ __( 'Any', 'woocommerce' ) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
) }
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Display Style', 'woocommerce' ) }
|
||||
value={ displayStyle }
|
||||
onChange={ ( value: string ) =>
|
||||
setAttributes( {
|
||||
displayStyle: value,
|
||||
} )
|
||||
}
|
||||
className="wc-block-attribute-filter__display-toggle"
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value="list"
|
||||
label={ __( 'List', 'woocommerce' ) }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value="dropdown"
|
||||
label={ __( 'Dropdown', 'woocommerce' ) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __( 'Content Settings', 'woocommerce' ) }
|
||||
initialOpen={ false }
|
||||
>
|
||||
<AttributeSelectControls
|
||||
isCompact={ true }
|
||||
attributeId={ attributeId }
|
||||
setAttributeId={ ( id: number ) => {
|
||||
setAttributes( {
|
||||
attributeId: id,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { AttributeSetting } from '@woocommerce/types';
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
ComboboxControl,
|
||||
PanelBody,
|
||||
SelectControl,
|
||||
ToggleControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { sortOrderOptions } from '../constants';
|
||||
import { BlockAttributes, EditProps } from '../types';
|
||||
|
||||
const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] );
|
||||
|
||||
export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
|
||||
const {
|
||||
attributeId,
|
||||
sortOrder,
|
||||
queryType,
|
||||
displayStyle,
|
||||
showCounts,
|
||||
hideEmpty,
|
||||
clearButton,
|
||||
} = attributes;
|
||||
|
||||
return (
|
||||
<>
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody title={ __( 'Attribute', 'woocommerce' ) }>
|
||||
<ComboboxControl
|
||||
options={ ATTRIBUTES.map( ( item ) => ( {
|
||||
value: item.attribute_id,
|
||||
label: item.attribute_label,
|
||||
} ) ) }
|
||||
value={ attributeId + '' }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( {
|
||||
attributeId: parseInt( value || '', 10 ),
|
||||
} )
|
||||
}
|
||||
help={ __(
|
||||
'Choose the attribute to show in this filter.',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody title={ __( 'Settings', 'woocommerce' ) }>
|
||||
<SelectControl
|
||||
label={ __( 'Sort order', 'woocommerce' ) }
|
||||
value={ sortOrder }
|
||||
options={ [
|
||||
{
|
||||
value: '',
|
||||
label: __( 'Select an option', 'woocommerce' ),
|
||||
disabled: true,
|
||||
},
|
||||
...sortOrderOptions,
|
||||
] }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( { sortOrder: value } )
|
||||
}
|
||||
help={ __(
|
||||
'Determine the order of filter options.',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Logic', 'woocommerce' ) }
|
||||
value={ queryType }
|
||||
onChange={ ( value: BlockAttributes[ 'queryType' ] ) =>
|
||||
setAttributes( { queryType: value } )
|
||||
}
|
||||
style={ { width: '100%' } }
|
||||
help={
|
||||
queryType === 'and'
|
||||
? createInterpolateElement(
|
||||
__(
|
||||
'Show results for <b>all</b> selected attributes. Displayed products must contain <b>all of them</b> to appear in the results.',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
b: <strong />,
|
||||
}
|
||||
)
|
||||
: __(
|
||||
'Show results for any of the attributes selected (displayed products don’t have to have them all).',
|
||||
'woocommerce'
|
||||
)
|
||||
}
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
label={ __( 'Any', 'woocommerce' ) }
|
||||
value="or"
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
label={ __( 'All', 'woocommerce' ) }
|
||||
value="and"
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<InspectorControls group="styles">
|
||||
<PanelBody title={ __( 'Display', 'woocommerce' ) }>
|
||||
<ToggleGroupControl
|
||||
value={ displayStyle }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'displayStyle' ]
|
||||
) => setAttributes( { displayStyle: value } ) }
|
||||
style={ { width: '100%' } }
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
label={ __( 'List', 'woocommerce' ) }
|
||||
value="list"
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
label={ __( 'Chips', 'woocommerce' ) }
|
||||
value="chips"
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
<ToggleControl
|
||||
label={ __( 'Product counts', 'woocommerce' ) }
|
||||
checked={ showCounts }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( { showCounts: value } )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Empty filter options', 'woocommerce' ) }
|
||||
checked={ ! hideEmpty }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( { hideEmpty: ! value } )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Clear button', 'woocommerce' ) }
|
||||
checked={ clearButton }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( { clearButton: value } )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,7 +1,12 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export const attributeOptionsPreview = [
|
||||
{
|
||||
id: 23,
|
||||
name: 'Blue',
|
||||
name: __( 'Blue', 'woocommerce' ),
|
||||
slug: 'blue',
|
||||
attr_slug: 'blue',
|
||||
description: '',
|
||||
|
@ -10,7 +15,7 @@ export const attributeOptionsPreview = [
|
|||
},
|
||||
{
|
||||
id: 29,
|
||||
name: 'Gray',
|
||||
name: __( 'Gray', 'woocommerce' ),
|
||||
slug: 'gray',
|
||||
attr_slug: 'gray',
|
||||
description: '',
|
||||
|
@ -19,7 +24,7 @@ export const attributeOptionsPreview = [
|
|||
},
|
||||
{
|
||||
id: 24,
|
||||
name: 'Green',
|
||||
name: __( 'Green', 'woocommerce' ),
|
||||
slug: 'green',
|
||||
attr_slug: 'green',
|
||||
description: '',
|
||||
|
@ -28,7 +33,7 @@ export const attributeOptionsPreview = [
|
|||
},
|
||||
{
|
||||
id: 25,
|
||||
name: 'Red',
|
||||
name: __( 'Red', 'woocommerce' ),
|
||||
slug: 'red',
|
||||
attr_slug: 'red',
|
||||
description: '',
|
||||
|
@ -37,7 +42,7 @@ export const attributeOptionsPreview = [
|
|||
},
|
||||
{
|
||||
id: 30,
|
||||
name: 'Yellow',
|
||||
name: __( 'Yellow', 'woocommerce' ),
|
||||
slug: 'yellow',
|
||||
attr_slug: 'yellow',
|
||||
description: '',
|
||||
|
@ -45,3 +50,17 @@ export const attributeOptionsPreview = [
|
|||
count: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export const sortOrders = {
|
||||
'name-asc': __( 'Name, A to Z', 'woocommerce' ),
|
||||
'name-desc': __( 'Name, Z to A', 'woocommerce' ),
|
||||
'count-desc': __( 'Most results first', 'woocommerce' ),
|
||||
'count-asc': __( 'Least results first', 'woocommerce' ),
|
||||
};
|
||||
|
||||
export const sortOrderOptions = Object.entries( sortOrders ).map(
|
||||
( [ value, label ] ) => ( {
|
||||
label,
|
||||
value,
|
||||
} )
|
||||
);
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useCallback, useEffect, useState } from '@wordpress/element';
|
||||
import { BlockControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import {
|
||||
useCollection,
|
||||
|
@ -14,26 +14,16 @@ import {
|
|||
AttributeTerm,
|
||||
objectHasProp,
|
||||
} from '@woocommerce/types';
|
||||
import {
|
||||
Disabled,
|
||||
Button,
|
||||
ToolbarGroup,
|
||||
withSpokenMessages,
|
||||
Notice,
|
||||
} from '@wordpress/components';
|
||||
import { Disabled, withSpokenMessages, Notice } from '@wordpress/components';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { EditProps, isAttributeCounts } from './types';
|
||||
import {
|
||||
NoAttributesPlaceholder,
|
||||
AttributesPlaceholder,
|
||||
} from './components/placeholder';
|
||||
import { AttributeSelectControls } from './components/attribute-select-controls';
|
||||
import { NoAttributesPlaceholder } from './components/placeholder';
|
||||
import { getAttributeFromId } from './utils';
|
||||
import { Inspector } from './components/inspector-controls';
|
||||
import { Inspector } from './components/inspector';
|
||||
import { AttributeCheckboxList } from './components/attribute-checkbox-list';
|
||||
import { AttributeDropdown } from './components/attribute-dropdown';
|
||||
import { attributeOptionsPreview } from './constants';
|
||||
|
@ -41,84 +31,21 @@ import './style.scss';
|
|||
|
||||
const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] );
|
||||
|
||||
const Toolbar = ( {
|
||||
onClick,
|
||||
isEditing,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
isEditing: boolean;
|
||||
} ) => (
|
||||
<BlockControls>
|
||||
<ToolbarGroup
|
||||
controls={ [
|
||||
{
|
||||
icon: 'edit',
|
||||
title: __( 'Edit', 'woocommerce' ),
|
||||
onClick,
|
||||
isActive: isEditing,
|
||||
},
|
||||
] }
|
||||
/>
|
||||
</BlockControls>
|
||||
);
|
||||
|
||||
const Wrapper = ( {
|
||||
children,
|
||||
onClickToolbarEdit,
|
||||
isEditing,
|
||||
blockProps,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
onClickToolbarEdit: () => void;
|
||||
isEditing: boolean;
|
||||
blockProps: object;
|
||||
} ) => (
|
||||
<div { ...blockProps }>
|
||||
<Toolbar onClick={ onClickToolbarEdit } isEditing={ isEditing } />
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
||||
const AttributeSelectPlaceholder = ( {
|
||||
attributeId,
|
||||
setAttributeId,
|
||||
onClickDone,
|
||||
}: {
|
||||
attributeId: number;
|
||||
setAttributeId: ( id: number ) => void;
|
||||
onClickDone: () => void;
|
||||
} ) => (
|
||||
<AttributesPlaceholder>
|
||||
<div className="wc-block-attribute-filter__selection">
|
||||
<AttributeSelectControls
|
||||
isCompact={ false }
|
||||
attributeId={ attributeId }
|
||||
setAttributeId={ setAttributeId }
|
||||
/>
|
||||
<Button variant="primary" onClick={ onClickDone }>
|
||||
{ __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</AttributesPlaceholder>
|
||||
);
|
||||
|
||||
const Edit = ( props: EditProps ) => {
|
||||
const {
|
||||
attributes: blockAttributes,
|
||||
setAttributes,
|
||||
debouncedSpeak,
|
||||
clientId,
|
||||
} = props;
|
||||
const { attributes: blockAttributes, clientId } = props;
|
||||
|
||||
const { attributeId, queryType, isPreview, displayStyle, showCounts } =
|
||||
blockAttributes;
|
||||
const {
|
||||
attributeId,
|
||||
queryType,
|
||||
isPreview,
|
||||
displayStyle,
|
||||
showCounts,
|
||||
sortOrder,
|
||||
hideEmpty,
|
||||
} = blockAttributes;
|
||||
|
||||
const attributeObject = getAttributeFromId( attributeId );
|
||||
|
||||
const [ isEditing, setIsEditing ] = useState(
|
||||
! attributeId && ! isPreview
|
||||
);
|
||||
|
||||
const [ attributeOptions, setAttributeOptions ] = useState<
|
||||
AttributeTerm[]
|
||||
>( [] );
|
||||
|
@ -130,7 +57,7 @@ const Edit = ( props: EditProps ) => {
|
|||
resourceName: 'products/attributes/terms',
|
||||
resourceValues: [ attributeObject?.id || 0 ],
|
||||
shouldSelect: blockAttributes.attributeId > 0,
|
||||
query: { orderby: 'menu_order' },
|
||||
query: { orderby: 'menu_order', hide_empty: hideEmpty },
|
||||
} );
|
||||
|
||||
const { results: filteredCounts } = useCollectionData( {
|
||||
|
@ -183,8 +110,6 @@ const Edit = ( props: EditProps ) => {
|
|||
[ clientId ]
|
||||
);
|
||||
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
useEffect( () => {
|
||||
const termIdHasProducts =
|
||||
objectHasProp( filteredCounts, 'attribute_counts' ) &&
|
||||
|
@ -192,14 +117,31 @@ const Edit = ( props: EditProps ) => {
|
|||
? filteredCounts.attribute_counts.map( ( term ) => term.term )
|
||||
: [];
|
||||
|
||||
if ( termIdHasProducts.length === 0 ) return setAttributeOptions( [] );
|
||||
if ( termIdHasProducts.length === 0 && hideEmpty )
|
||||
return setAttributeOptions( [] );
|
||||
|
||||
setAttributeOptions(
|
||||
attributeTerms.filter( ( term ) => {
|
||||
return termIdHasProducts.includes( term.id );
|
||||
} )
|
||||
attributeTerms
|
||||
.filter( ( term ) => {
|
||||
if ( hideEmpty )
|
||||
return termIdHasProducts.includes( term.id );
|
||||
return true;
|
||||
} )
|
||||
.sort( ( a, b ) => {
|
||||
switch ( sortOrder ) {
|
||||
case 'name-asc':
|
||||
return a.name > b.name ? 1 : -1;
|
||||
case 'name-desc':
|
||||
return a.name < b.name ? 1 : -1;
|
||||
case 'count-asc':
|
||||
return a.count > b.count ? 1 : -1;
|
||||
case 'count-desc':
|
||||
default:
|
||||
return a.count < b.count ? 1 : -1;
|
||||
}
|
||||
} )
|
||||
);
|
||||
}, [ attributeTerms, filteredCounts ] );
|
||||
}, [ attributeTerms, filteredCounts, sortOrder, hideEmpty ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( productFilterWrapperBlockId ) {
|
||||
|
@ -231,36 +173,16 @@ const Edit = ( props: EditProps ) => {
|
|||
updateBlockAttributes,
|
||||
] );
|
||||
|
||||
const onClickDone = useCallback( () => {
|
||||
setIsEditing( false );
|
||||
debouncedSpeak(
|
||||
__(
|
||||
'Now displaying a preview of the Filter Products by Attribute block.',
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
}, [ setIsEditing ] );
|
||||
|
||||
const setAttributeId = useCallback(
|
||||
( id ) => {
|
||||
setAttributes( {
|
||||
attributeId: id,
|
||||
} );
|
||||
},
|
||||
[ setAttributes ]
|
||||
const Wrapper = ( { children }: { children: React.ReactNode } ) => (
|
||||
<div { ...useBlockProps() }>
|
||||
<Inspector { ...props } />
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
||||
const toggleEditing = useCallback( () => {
|
||||
setIsEditing( ! isEditing );
|
||||
}, [ isEditing ] );
|
||||
|
||||
if ( isPreview ) {
|
||||
return (
|
||||
<Wrapper
|
||||
onClickToolbarEdit={ toggleEditing }
|
||||
isEditing={ isEditing }
|
||||
blockProps={ blockProps }
|
||||
>
|
||||
<Wrapper>
|
||||
<Disabled>
|
||||
<AttributeCheckboxList
|
||||
showCounts={ showCounts }
|
||||
|
@ -274,37 +196,14 @@ const Edit = ( props: EditProps ) => {
|
|||
// Block rendering starts.
|
||||
if ( Object.keys( ATTRIBUTES ).length === 0 )
|
||||
return (
|
||||
<Wrapper
|
||||
onClickToolbarEdit={ toggleEditing }
|
||||
isEditing={ isEditing }
|
||||
blockProps={ blockProps }
|
||||
>
|
||||
<Wrapper>
|
||||
<NoAttributesPlaceholder />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
if ( isEditing )
|
||||
return (
|
||||
<Wrapper
|
||||
onClickToolbarEdit={ toggleEditing }
|
||||
isEditing={ isEditing }
|
||||
blockProps={ blockProps }
|
||||
>
|
||||
<AttributeSelectPlaceholder
|
||||
onClickDone={ onClickDone }
|
||||
attributeId={ attributeId }
|
||||
setAttributeId={ setAttributeId }
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
if ( ! attributeId || ! attributeObject )
|
||||
return (
|
||||
<Wrapper
|
||||
onClickToolbarEdit={ toggleEditing }
|
||||
isEditing={ isEditing }
|
||||
blockProps={ blockProps }
|
||||
>
|
||||
<Wrapper>
|
||||
<Notice status="warning" isDismissible={ false }>
|
||||
<p>
|
||||
{ __(
|
||||
|
@ -318,11 +217,7 @@ const Edit = ( props: EditProps ) => {
|
|||
|
||||
if ( attributeOptions.length === 0 )
|
||||
return (
|
||||
<Wrapper
|
||||
onClickToolbarEdit={ toggleEditing }
|
||||
isEditing={ isEditing }
|
||||
blockProps={ blockProps }
|
||||
>
|
||||
<Wrapper>
|
||||
<Notice status="warning" isDismissible={ false }>
|
||||
<p>
|
||||
{ __(
|
||||
|
@ -335,12 +230,7 @@ const Edit = ( props: EditProps ) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
onClickToolbarEdit={ toggleEditing }
|
||||
isEditing={ isEditing }
|
||||
blockProps={ blockProps }
|
||||
>
|
||||
<Inspector { ...props } />
|
||||
<Wrapper>
|
||||
<Disabled>
|
||||
{ displayStyle === 'dropdown' ? (
|
||||
<AttributeDropdown
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
|
||||
import { productFilterOptions } from '@woocommerce/icons';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { AttributeSetting } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import metadata from './block.json';
|
||||
import Edit from './edit';
|
||||
import './style.scss';
|
||||
|
||||
if ( isExperimentalBlocksEnabled() ) {
|
||||
const defaultAttribute = getSetting< AttributeSetting >(
|
||||
'defaultProductFilterAttribute'
|
||||
);
|
||||
|
||||
registerBlockType( metadata, {
|
||||
edit: Edit,
|
||||
icon: productFilterOptions,
|
||||
attributes: {
|
||||
...metadata.attributes,
|
||||
attributeId: {
|
||||
...metadata.attributes.attributeId,
|
||||
default: parseInt( defaultAttribute.attribute_id, 10 ),
|
||||
},
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -3,13 +3,21 @@
|
|||
*/
|
||||
import { BlockEditProps } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { sortOrders } from './constants';
|
||||
|
||||
export type BlockAttributes = {
|
||||
attributeId: number;
|
||||
showCounts: boolean;
|
||||
queryType: string;
|
||||
queryType: 'or' | 'and';
|
||||
displayStyle: string;
|
||||
selectType: string;
|
||||
isPreview: boolean;
|
||||
sortOrder: keyof typeof sortOrders;
|
||||
hideEmpty: boolean;
|
||||
clearButton: boolean;
|
||||
};
|
||||
|
||||
export interface EditProps extends BlockEditProps< BlockAttributes > {
|
||||
|
|
|
@ -1,34 +1,40 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
InnerBlocks,
|
||||
useBlockProps,
|
||||
useInnerBlocksProps,
|
||||
InspectorControls,
|
||||
} from '@wordpress/block-editor';
|
||||
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useCollection } from '@woocommerce/base-context/hooks';
|
||||
import { AttributeTerm } from '@woocommerce/types';
|
||||
import {
|
||||
PanelBody,
|
||||
RadioControl,
|
||||
Spinner,
|
||||
ExternalLink,
|
||||
RangeControl,
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
} from '@wordpress/components';
|
||||
import { Icon, settings, menu } from '@wordpress/icons';
|
||||
import { filter, filterThreeLines } from '@woocommerce/icons';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { AttributeSetting } from '@woocommerce/types';
|
||||
import {
|
||||
InnerBlocks,
|
||||
InspectorControls,
|
||||
useBlockProps,
|
||||
useInnerBlocksProps,
|
||||
} from '@wordpress/block-editor';
|
||||
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, menu, settings } from '@wordpress/icons';
|
||||
import {
|
||||
ExternalLink,
|
||||
PanelBody,
|
||||
RadioControl,
|
||||
RangeControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { BlockAttributes } from './types';
|
||||
import './editor.scss';
|
||||
import type { BlockAttributes } from './types';
|
||||
|
||||
const defaultAttribute = getSetting< AttributeSetting >(
|
||||
'defaultProductFilterAttribute'
|
||||
);
|
||||
|
||||
const TEMPLATE: InnerBlockTemplate[] = [
|
||||
[
|
||||
|
@ -64,8 +70,8 @@ const TEMPLATE: InnerBlockTemplate[] = [
|
|||
'woocommerce/product-filter',
|
||||
{
|
||||
filterType: 'attribute-filter',
|
||||
heading: __( 'Attribute', 'woocommerce' ),
|
||||
attributeId: 0,
|
||||
heading: defaultAttribute.attribute_label,
|
||||
attributeId: parseInt( defaultAttribute.attribute_id, 10 ),
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -101,73 +107,11 @@ const TEMPLATE: InnerBlockTemplate[] = [
|
|||
],
|
||||
];
|
||||
|
||||
const addHighestProductCountAttributeToTemplate = (
|
||||
template: InnerBlockTemplate[],
|
||||
highestProductCountAttribute: AttributeTerm | null
|
||||
): InnerBlockTemplate[] => {
|
||||
if ( highestProductCountAttribute === null ) return template;
|
||||
|
||||
return template.map( ( block ) => {
|
||||
const blockNameIndex = 0;
|
||||
const blockAttributesIndex = 1;
|
||||
const blockName = block[ blockNameIndex ];
|
||||
const blockAttributes = block[ blockAttributesIndex ];
|
||||
if (
|
||||
blockName === 'woocommerce/product-filter' &&
|
||||
blockAttributes?.filterType === 'attribute-filter'
|
||||
) {
|
||||
return [
|
||||
blockName,
|
||||
{
|
||||
...blockAttributes,
|
||||
heading: highestProductCountAttribute.name,
|
||||
attributeId: highestProductCountAttribute.id,
|
||||
metadata: {
|
||||
name: sprintf(
|
||||
/* translators: %s is referring to the filter attribute name. For example: Color, Size, etc. */
|
||||
__( '%s (Experimental)', 'woocommerce' ),
|
||||
highestProductCountAttribute.name
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return block;
|
||||
} );
|
||||
};
|
||||
|
||||
export const Edit = ( {
|
||||
setAttributes,
|
||||
attributes,
|
||||
}: BlockEditProps< BlockAttributes > ) => {
|
||||
const blockProps = useBlockProps();
|
||||
const { results: attributeTerms, isLoading } =
|
||||
useCollection< AttributeTerm >( {
|
||||
namespace: '/wc/store/v1',
|
||||
resourceName: 'products/attributes',
|
||||
} );
|
||||
|
||||
const highestProductCountAttribute =
|
||||
attributeTerms.reduce< AttributeTerm | null >(
|
||||
( attributeWithMostProducts, attribute ) => {
|
||||
if ( attributeWithMostProducts === null ) {
|
||||
return attribute;
|
||||
}
|
||||
return attribute.count > attributeWithMostProducts.count
|
||||
? attribute
|
||||
: attributeWithMostProducts;
|
||||
},
|
||||
null
|
||||
);
|
||||
const updatedTemplate = addHighestProductCountAttributeToTemplate(
|
||||
TEMPLATE,
|
||||
highestProductCountAttribute
|
||||
);
|
||||
|
||||
if ( isLoading ) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
const templatePartEditUri = getSetting< string >(
|
||||
'templatePartProductFiltersOverlayEditUri',
|
||||
|
@ -329,7 +273,7 @@ export const Edit = ( {
|
|||
) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<InnerBlocks templateLock={ false } template={ updatedTemplate } />
|
||||
<InnerBlocks templateLock={ false } template={ TEMPLATE } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { test as base, expect } from '@woocommerce/e2e-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductFiltersPage } from './product-filters.page';
|
||||
|
||||
const blockData = {
|
||||
name: 'woocommerce/product-filter-attribute',
|
||||
selectors: {
|
||||
frontend: {},
|
||||
editor: {
|
||||
settings: {},
|
||||
},
|
||||
},
|
||||
slug: 'archive-product',
|
||||
};
|
||||
|
||||
const test = base.extend< { pageObject: ProductFiltersPage } >( {
|
||||
pageObject: async ( { page, editor, frontendUtils }, use ) => {
|
||||
const pageObject = new ProductFiltersPage( {
|
||||
page,
|
||||
editor,
|
||||
frontendUtils,
|
||||
} );
|
||||
await use( pageObject );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe( `${ blockData.name }`, () => {
|
||||
test.beforeEach( async ( { admin, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//${ blockData.slug }`,
|
||||
postType: 'wp_template',
|
||||
canvas: 'edit',
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'should display the correct inspector style controls', async ( {
|
||||
editor,
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas
|
||||
.getByLabel( 'Block: Attribute (Experimental)' )
|
||||
.getByLabel( 'Block: Filter Options' );
|
||||
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
await block.click();
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
await editor.page.getByRole( 'tab', { name: 'Styles' } ).click();
|
||||
|
||||
await expect(
|
||||
editor.page.getByText( 'ColorAll options are currently hidden' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
editor.page.getByText(
|
||||
'TypographyAll options are currently hidden'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
editor.page.getByText(
|
||||
'DimensionsAll options are currently hidden'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
editor.page.getByText( 'DisplayListChips' )
|
||||
).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'should display the correct inspector setting controls', async ( {
|
||||
editor,
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas
|
||||
.getByLabel( 'Block: Attribute (Experimental)' )
|
||||
.getByLabel( 'Block: Filter Options' );
|
||||
|
||||
await expect( block ).toBeVisible();
|
||||
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
await block.click();
|
||||
|
||||
await expect(
|
||||
editor.page.getByLabel( 'Editor settings' ).getByRole( 'button', {
|
||||
name: 'Attribute',
|
||||
exact: true,
|
||||
} )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
editor.page
|
||||
.getByLabel( 'Editor settings' )
|
||||
.getByRole( 'button', { name: 'Settings', exact: true } )
|
||||
).toBeVisible();
|
||||
await expect( editor.page.getByText( 'Sort order' ) ).toBeVisible();
|
||||
await expect( editor.page.getByText( 'LogicAnyAll' ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'should dynamically set block title and heading based on the selected attribute', async ( {
|
||||
editor,
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
const block = editor.canvas
|
||||
.getByLabel( 'Block: Attribute (Experimental)' )
|
||||
.getByLabel( 'Block: Filter Options' );
|
||||
|
||||
await editor.openDocumentSettingsSidebar();
|
||||
await block.click();
|
||||
|
||||
await editor.page
|
||||
.getByRole( 'tabpanel' )
|
||||
.getByRole( 'combobox' )
|
||||
.first()
|
||||
.click();
|
||||
await editor.page
|
||||
.getByRole( 'option', { name: 'Size', exact: true } )
|
||||
.click();
|
||||
|
||||
await pageObject.page.getByLabel( 'Document Overview' ).click();
|
||||
const listView = pageObject.page.getByLabel( 'List View' );
|
||||
|
||||
await expect( listView ).toBeVisible();
|
||||
|
||||
const productFilterAttributeSizeBlockListItem = listView.getByText(
|
||||
'Size (Experimental)' // it must select the attribute with the highest product count
|
||||
);
|
||||
await expect( productFilterAttributeSizeBlockListItem ).toBeVisible();
|
||||
|
||||
const productFilterAttributeWrapperBlock = editor.canvas.getByLabel(
|
||||
'Block: Attribute (Experimental)'
|
||||
);
|
||||
await editor.selectBlocks( productFilterAttributeWrapperBlock );
|
||||
await expect( productFilterAttributeWrapperBlock ).toBeVisible();
|
||||
|
||||
const productFilterAttributeBlockHeading =
|
||||
productFilterAttributeWrapperBlock.getByText( 'Size', {
|
||||
exact: true,
|
||||
} );
|
||||
|
||||
await expect( productFilterAttributeBlockHeading ).toBeVisible();
|
||||
} );
|
||||
} );
|
|
@ -439,72 +439,4 @@ test.describe( `${ blockData.name }`, () => {
|
|||
block.locator( blockData.selectors.editor.layoutWrapper )
|
||||
).toHaveCSS( 'gap', '0px' );
|
||||
} );
|
||||
|
||||
test.describe( 'product-filter-attribute', () => {
|
||||
test( 'should dynamically set block title and heading based on the selected attribute', async ( {
|
||||
editor,
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.addProductFiltersBlock( { cleanContent: true } );
|
||||
|
||||
await pageObject.page.getByLabel( 'Document Overview' ).click();
|
||||
const listView = pageObject.page.getByLabel( 'List View' );
|
||||
|
||||
const productFiltersBlockListItem = listView.getByRole( 'link', {
|
||||
name: 'Product Filters (Experimental)',
|
||||
} );
|
||||
await expect( productFiltersBlockListItem ).toBeVisible();
|
||||
const listViewExpander =
|
||||
pageObject.page.getByTestId( 'list-view-expander' );
|
||||
const listViewExpanderIcon = listViewExpander.locator( 'svg' );
|
||||
|
||||
await listViewExpanderIcon.click();
|
||||
|
||||
const productFilterAttributeColorBlockListItem = listView.getByText(
|
||||
'Color (Experimental)' // it must select the attribute with the highest product count
|
||||
);
|
||||
await expect(
|
||||
productFilterAttributeColorBlockListItem
|
||||
).toBeVisible();
|
||||
|
||||
const productFilterAttributeBlock = editor.canvas
|
||||
.getByLabel( 'Block: Filter Options' )
|
||||
.and(
|
||||
editor.canvas.locator(
|
||||
'[data-type="woocommerce/product-filter-attribute"]'
|
||||
)
|
||||
);
|
||||
await editor.selectBlocks( productFilterAttributeBlock );
|
||||
await editor.clickBlockToolbarButton( 'Edit' );
|
||||
await editor.canvas
|
||||
.locator( 'label' )
|
||||
.filter( { hasText: 'Size' } )
|
||||
.click();
|
||||
await editor.canvas.getByRole( 'button', { name: 'Done' } ).click();
|
||||
|
||||
await expect(
|
||||
productFilterAttributeColorBlockListItem
|
||||
).toBeHidden();
|
||||
|
||||
const productFilterAttributeSizeBlockListItem = listView.getByText(
|
||||
'Size (Experimental)' // it must select the attribute with the highest product count
|
||||
);
|
||||
await expect(
|
||||
productFilterAttributeSizeBlockListItem
|
||||
).toBeVisible();
|
||||
|
||||
const productFilterAttributeWrapperBlock = editor.canvas.getByLabel(
|
||||
'Block: Attribute (Experimental)'
|
||||
);
|
||||
await editor.selectBlocks( productFilterAttributeWrapperBlock );
|
||||
await expect( productFilterAttributeWrapperBlock ).toBeVisible();
|
||||
|
||||
const productFilterAttributeBlockHeading =
|
||||
productFilterAttributeWrapperBlock.getByText( 'Size', {
|
||||
exact: true,
|
||||
} );
|
||||
|
||||
await expect( productFilterAttributeBlockHeading ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
Comment: [Experimental] Attribute Filter: Update Inspector Control Settings
|
||||
|
||||
|
|
@ -30,6 +30,33 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
|
||||
add_filter( 'collection_filter_query_param_keys', array( $this, 'get_filter_query_param_keys' ), 10, 2 );
|
||||
add_filter( 'collection_active_filters_data', array( $this, 'register_active_filters_data' ), 10, 2 );
|
||||
add_action( 'deleted_transient', array( $this, 'delete_default_attribute_id_transient' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = array() ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
if ( is_admin() ) {
|
||||
$this->asset_data_registry->add( 'defaultProductFilterAttribute', $this->get_default_attribute() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the default attribute id transient when the attribute taxonomies are deleted.
|
||||
*
|
||||
* @param string $transient The transient name.
|
||||
*/
|
||||
public function delete_default_attribute_id_transient( $transient ) {
|
||||
if ( 'wc_attribute_taxonomies' === $transient ) {
|
||||
delete_transient( 'wc_block_product_filter_attribute_default_attribute' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +70,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
|
||||
$attribute_param_keys = array_filter(
|
||||
$url_param_keys,
|
||||
function( $param ) {
|
||||
function ( $param ) {
|
||||
return strpos( $param, 'filter_' ) === 0 || strpos( $param, 'query_type_' ) === 0;
|
||||
}
|
||||
);
|
||||
|
@ -64,7 +91,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
public function register_active_filters_data( $data, $params ) {
|
||||
$product_attributes_map = array_reduce(
|
||||
wc_get_attribute_taxonomies(),
|
||||
function( $acc, $attribute_object ) {
|
||||
function ( $acc, $attribute_object ) {
|
||||
$acc[ $attribute_object->attribute_name ] = $attribute_object->attribute_label;
|
||||
return $acc;
|
||||
},
|
||||
|
@ -73,7 +100,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
|
||||
$active_product_attributes = array_reduce(
|
||||
array_keys( $params ),
|
||||
function( $acc, $attribute ) {
|
||||
function ( $acc, $attribute ) {
|
||||
if ( strpos( $attribute, 'filter_' ) === 0 ) {
|
||||
$acc[] = str_replace( 'filter_', '', $attribute );
|
||||
}
|
||||
|
@ -84,7 +111,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
|
||||
$active_product_attributes = array_filter(
|
||||
$active_product_attributes,
|
||||
function( $item ) use ( $product_attributes_map ) {
|
||||
function ( $item ) use ( $product_attributes_map ) {
|
||||
return in_array( $item, array_keys( $product_attributes_map ), true );
|
||||
}
|
||||
);
|
||||
|
@ -96,7 +123,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
|
||||
// Get attribute term by slug.
|
||||
$terms = array_map(
|
||||
function( $term ) use ( $product_attribute, $action_namespace ) {
|
||||
function ( $term ) use ( $product_attribute, $action_namespace ) {
|
||||
$term_object = get_term_by( 'slug', $term, "pa_{$product_attribute}" );
|
||||
return array(
|
||||
'title' => $term_object->name,
|
||||
|
@ -168,7 +195,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
);
|
||||
|
||||
$attribute_options = array_map(
|
||||
function( $term ) use ( $attribute_counts, $selected_terms ) {
|
||||
function ( $term ) use ( $attribute_counts, $selected_terms ) {
|
||||
$term = (array) $term;
|
||||
$term['count'] = $attribute_counts[ $term['term_id'] ];
|
||||
$term['selected'] = in_array( $term['slug'], $selected_terms, true );
|
||||
|
@ -179,7 +206,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
|
||||
$filtered_options = array_filter(
|
||||
$attribute_options,
|
||||
function( $option ) {
|
||||
function ( $option ) {
|
||||
return $option['count'] > 0;
|
||||
}
|
||||
);
|
||||
|
@ -191,7 +218,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
$context = array(
|
||||
'attributeSlug' => str_replace( 'pa_', '', $product_attribute->slug ),
|
||||
'queryType' => $attributes['queryType'],
|
||||
'selectType' => $attributes['selectType'],
|
||||
'selectType' => 'multiple',
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
|
@ -242,7 +269,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
'items' => $list_items,
|
||||
'action' => "{$this->get_full_block_name()}::actions.navigate",
|
||||
'selected_items' => $selected_items,
|
||||
'select_type' => $attributes['selectType'] ?? 'multiple',
|
||||
'select_type' => 'multiple',
|
||||
// translators: %s is a product attribute name.
|
||||
'placeholder' => sprintf( __( 'Select %s', 'woocommerce' ), $product_attribute->name ),
|
||||
)
|
||||
|
@ -264,7 +291,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
$show_counts = $attributes['showCounts'] ?? false;
|
||||
|
||||
$list_options = array_map(
|
||||
function( $option ) use ( $show_counts ) {
|
||||
function ( $option ) use ( $show_counts ) {
|
||||
return array(
|
||||
'id' => $option['slug'] . '-' . $option['term_id'],
|
||||
'checked' => $option['selected'],
|
||||
|
@ -320,13 +347,72 @@ final class ProductFilterAttribute extends AbstractBlock {
|
|||
|
||||
$attribute_counts = array_reduce(
|
||||
$attribute_counts,
|
||||
function( $acc, $count ) {
|
||||
function ( $acc, $count ) {
|
||||
$acc[ $count['term'] ] = $count['count'];
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
array()
|
||||
);
|
||||
|
||||
return $attribute_counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attribute if with most term but closest to 30 terms.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_default_attribute() {
|
||||
$cached = get_transient( 'wc_block_product_filter_attribute_default_attribute' );
|
||||
|
||||
if ( $cached ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$attributes = wc_get_attribute_taxonomies();
|
||||
|
||||
$attributes_count = array_map(
|
||||
function ( $attribute ) {
|
||||
return intval(
|
||||
wp_count_terms(
|
||||
array(
|
||||
'taxonomy' => 'pa_' . $attribute->attribute_name,
|
||||
'hide_empty' => false,
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
$attributes
|
||||
);
|
||||
|
||||
asort( $attributes_count );
|
||||
|
||||
$search = 30;
|
||||
$closest = null;
|
||||
$attribute_id = null;
|
||||
|
||||
foreach ( $attributes_count as $id => $count ) {
|
||||
if ( null === $closest || abs( $search - $closest ) > abs( $count - $search ) ) {
|
||||
$closest = $count;
|
||||
$attribute_id = $id;
|
||||
}
|
||||
|
||||
if ( $closest && $count >= $search ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$default_attribute = array(
|
||||
'id' => 0,
|
||||
'label' => __( 'Attribute', 'woocommerce' ),
|
||||
);
|
||||
|
||||
if ( $attribute_id ) {
|
||||
$default_attribute = $attributes[ $attribute_id ];
|
||||
}
|
||||
|
||||
set_transient( 'wc_block_product_filter_attribute_default_attribute', $default_attribute );
|
||||
|
||||
return $default_attribute;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue