Add a stock filter block powered by the interactivity API (https://github.com/woocommerce/woocommerce-blocks/pull/11663)
This commit is contained in:
parent
49579f4aa0
commit
d62b950aae
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "woocommerce/collection-stock-filter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"title": "Stock Filter",
|
||||||
|
"description": "Enable customers to filter the product collection by stock status.",
|
||||||
|
"category": "woocommerce",
|
||||||
|
"keywords": [ "WooCommerce", "filter", "stock" ],
|
||||||
|
"supports": {
|
||||||
|
"interactivity": true,
|
||||||
|
"html": false,
|
||||||
|
"multiple": false
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"className": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"showCounts": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"displayStyle": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "list"
|
||||||
|
},
|
||||||
|
"selectType": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "multiple"
|
||||||
|
},
|
||||||
|
"isPreview": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"queryParam": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {
|
||||||
|
"calculate_stock_status_counts": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"usesContext": [ "collectionData" ],
|
||||||
|
"ancestor": [ "woocommerce/collection-filters" ],
|
||||||
|
"textdomain": "woo-gutenberg-products-block",
|
||||||
|
"apiVersion": 2,
|
||||||
|
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { InspectorControls } from '@wordpress/block-editor';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import {
|
||||||
|
PanelBody,
|
||||||
|
ToggleControl,
|
||||||
|
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||||
|
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||||
|
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||||
|
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||||
|
} from '@wordpress/components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { EditProps } from '../types';
|
||||||
|
|
||||||
|
export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
|
||||||
|
const { showCounts, selectType, displayStyle } = attributes;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InspectorControls key="inspector">
|
||||||
|
<PanelBody
|
||||||
|
title={ __(
|
||||||
|
'Display Settings',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
<ToggleControl
|
||||||
|
label={ __(
|
||||||
|
'Display product count',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
checked={ showCounts }
|
||||||
|
onChange={ () =>
|
||||||
|
setAttributes( {
|
||||||
|
showCounts: ! showCounts,
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ToggleGroupControl
|
||||||
|
label={ __(
|
||||||
|
'Allow selecting multiple options?',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
value={ selectType || 'multiple' }
|
||||||
|
onChange={ ( value: string ) =>
|
||||||
|
setAttributes( {
|
||||||
|
selectType: value,
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
className="wc-block-attribute-filter__multiple-toggle"
|
||||||
|
>
|
||||||
|
<ToggleGroupControlOption
|
||||||
|
value="multiple"
|
||||||
|
label={ __(
|
||||||
|
'Multiple',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
<ToggleGroupControlOption
|
||||||
|
value="single"
|
||||||
|
label={ __( 'Single', 'woo-gutenberg-products-block' ) }
|
||||||
|
/>
|
||||||
|
</ToggleGroupControl>
|
||||||
|
<ToggleGroupControl
|
||||||
|
label={ __(
|
||||||
|
'Display Style',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
value={ displayStyle }
|
||||||
|
onChange={ ( value ) =>
|
||||||
|
setAttributes( {
|
||||||
|
displayStyle: value,
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
className="wc-block-attribute-filter__display-toggle"
|
||||||
|
>
|
||||||
|
<ToggleGroupControlOption
|
||||||
|
value="list"
|
||||||
|
label={ __( 'List', 'woo-gutenberg-products-block' ) }
|
||||||
|
/>
|
||||||
|
<ToggleGroupControlOption
|
||||||
|
value="dropdown"
|
||||||
|
label={ __(
|
||||||
|
'Dropdown',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
</ToggleGroupControl>
|
||||||
|
</PanelBody>
|
||||||
|
</InspectorControls>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { useMemo } from '@wordpress/element';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { useBlockProps } from '@wordpress/block-editor';
|
||||||
|
import { Disabled } from '@wordpress/components';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { Icon, chevronDown } from '@wordpress/icons';
|
||||||
|
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
|
||||||
|
import { CheckboxList } from '@woocommerce/blocks-components';
|
||||||
|
import Label from '@woocommerce/base-components/filter-element-label';
|
||||||
|
import FormTokenField from '@woocommerce/base-components/form-token-field';
|
||||||
|
import type { BlockEditProps } from '@wordpress/blocks';
|
||||||
|
import { getSetting } from '@woocommerce/settings';
|
||||||
|
import {
|
||||||
|
useCollectionData,
|
||||||
|
useQueryStateByContext,
|
||||||
|
} from '@woocommerce/base-context/hooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { BlockProps } from './types';
|
||||||
|
import { Inspector } from './components/inspector';
|
||||||
|
|
||||||
|
type CollectionData = {
|
||||||
|
// attribute_counts: null | unknown;
|
||||||
|
// price_range: null | unknown;
|
||||||
|
// rating_counts: null | unknown;
|
||||||
|
stock_status_counts: StockStatusCount[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StockStatusCount = {
|
||||||
|
status: string;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Edit = ( props: BlockEditProps< BlockProps > & { context: Context } ) => {
|
||||||
|
const blockProps = useBlockProps( {
|
||||||
|
className: classnames(
|
||||||
|
'wc-block-stock-filter',
|
||||||
|
props.attributes.className
|
||||||
|
),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const { showCounts, displayStyle } = props.attributes;
|
||||||
|
const stockStatusOptions: Record< string, string > = getSetting(
|
||||||
|
'stockStatusOptions',
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [ queryState ] = useQueryStateByContext();
|
||||||
|
|
||||||
|
const { results: filteredCounts } = useCollectionData( {
|
||||||
|
queryStock: true,
|
||||||
|
queryState,
|
||||||
|
isEditor: true,
|
||||||
|
} );
|
||||||
|
|
||||||
|
const listOptions = useMemo( () => {
|
||||||
|
return Object.entries( stockStatusOptions ).map( ( [ key, value ] ) => {
|
||||||
|
const count =
|
||||||
|
// @ts-expect-error - there is a fault with useCollectionData types, it can be non-array.
|
||||||
|
( filteredCounts as CollectionData )?.stock_status_counts?.find(
|
||||||
|
( item: StockStatusCount ) => item.status === key
|
||||||
|
)?.count;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: key,
|
||||||
|
label: (
|
||||||
|
<Label
|
||||||
|
name={ value }
|
||||||
|
count={ showCounts && count ? Number( count ) : null }
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
}, [ stockStatusOptions, filteredCounts, showCounts ] );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
<div { ...blockProps }>
|
||||||
|
<Inspector { ...props } />
|
||||||
|
<Disabled>
|
||||||
|
<div
|
||||||
|
className={ classnames(
|
||||||
|
'wc-block-stock-filter',
|
||||||
|
`style-${ displayStyle }`,
|
||||||
|
{
|
||||||
|
'is-loading': false,
|
||||||
|
}
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
{ displayStyle === 'dropdown' ? (
|
||||||
|
<>
|
||||||
|
<FormTokenField
|
||||||
|
className={ classnames( {
|
||||||
|
'single-selection': true,
|
||||||
|
'is-loading': false,
|
||||||
|
} ) }
|
||||||
|
suggestions={ [] }
|
||||||
|
placeholder={ __(
|
||||||
|
'Select stock status',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
onChange={ () => null }
|
||||||
|
value={ [] }
|
||||||
|
/>
|
||||||
|
<Icon icon={ chevronDown } size={ 30 } />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<CheckboxList
|
||||||
|
className={ 'wc-block-stock-filter-list' }
|
||||||
|
options={ listOptions }
|
||||||
|
checked={ [] }
|
||||||
|
onChange={ () => null }
|
||||||
|
isLoading={ false }
|
||||||
|
isDisabled={ true }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
</Disabled>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Edit;
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
store as interactivityStore,
|
||||||
|
navigate,
|
||||||
|
} from '@woocommerce/interactivity';
|
||||||
|
import { DropdownContext } from '@woocommerce/interactivity-components/dropdown';
|
||||||
|
import { HTMLElementEvent } from '@woocommerce/types';
|
||||||
|
|
||||||
|
const getUrl = ( activeFilters: string ) => {
|
||||||
|
const url = new URL( window.location.href );
|
||||||
|
const { searchParams } = url;
|
||||||
|
|
||||||
|
if ( activeFilters !== '' ) {
|
||||||
|
searchParams.set( 'filter_stock_status', activeFilters );
|
||||||
|
} else {
|
||||||
|
searchParams.delete( 'filter_stock_status' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.href;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StockFilterState = {
|
||||||
|
filters: {
|
||||||
|
stockStatus: string;
|
||||||
|
activeFilters: string;
|
||||||
|
showDropdown: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ActionProps = {
|
||||||
|
state: StockFilterState;
|
||||||
|
event: HTMLElementEvent< HTMLInputElement >;
|
||||||
|
};
|
||||||
|
|
||||||
|
interactivityStore( {
|
||||||
|
state: {
|
||||||
|
filters: {
|
||||||
|
stockStatus: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
filters: {
|
||||||
|
navigate: ( { context }: { context: DropdownContext } ) => {
|
||||||
|
if ( context.woocommerceDropdown.selectedItem.value ) {
|
||||||
|
navigate(
|
||||||
|
getUrl( context.woocommerceDropdown.selectedItem.value )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateProducts: ( { event }: ActionProps ) => {
|
||||||
|
// get the active filters from the url:
|
||||||
|
const url = new URL( window.location.href );
|
||||||
|
const currentFilters =
|
||||||
|
url.searchParams.get( 'filter_stock_status' ) || '';
|
||||||
|
|
||||||
|
// split out the active filters into an array.
|
||||||
|
const filtersArr =
|
||||||
|
currentFilters === '' ? [] : currentFilters.split( ',' );
|
||||||
|
|
||||||
|
// if checked and not already in activeFilters, add to activeFilters
|
||||||
|
// if not checked and in activeFilters, remove from activeFilters.
|
||||||
|
if ( event.target.checked ) {
|
||||||
|
if ( ! currentFilters.includes( event.target.value ) ) {
|
||||||
|
filtersArr.push( event.target.value );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = filtersArr.indexOf( event.target.value );
|
||||||
|
if ( index > -1 ) {
|
||||||
|
filtersArr.splice( index, 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate( getUrl( filtersArr.join( ',' ) ) );
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} );
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { registerBlockType } from '@wordpress/blocks';
|
||||||
|
import { Icon, box } from '@wordpress/icons';
|
||||||
|
import { isExperimentalBuild } from '@woocommerce/block-settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import './style.scss';
|
||||||
|
import edit from './edit';
|
||||||
|
import metadata from './block.json';
|
||||||
|
|
||||||
|
if ( isExperimentalBuild() ) {
|
||||||
|
registerBlockType( metadata, {
|
||||||
|
icon: {
|
||||||
|
src: (
|
||||||
|
<Icon
|
||||||
|
icon={ box }
|
||||||
|
className="wc-block-editor-components-block-icon"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
edit,
|
||||||
|
} );
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import Label from '@woocommerce/base-components/filter-element-label';
|
||||||
|
|
||||||
|
export const previewOptions = [
|
||||||
|
{
|
||||||
|
value: 'preview-1',
|
||||||
|
name: 'In Stock',
|
||||||
|
label: <Label name="In Stock" count={ 3 } />,
|
||||||
|
textLabel: 'In Stock (3)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'preview-2',
|
||||||
|
name: 'Out of stock',
|
||||||
|
label: <Label name="Out of stock" count={ 3 } />,
|
||||||
|
textLabel: 'Out of stock (3)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'preview-3',
|
||||||
|
name: 'On backorder',
|
||||||
|
label: <Label name="On backorder" count={ 2 } />,
|
||||||
|
textLabel: 'On backorder (2)',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,181 @@
|
||||||
|
@import "../../../shared/styles/style";
|
||||||
|
|
||||||
|
// Import styles we need to render the checkbox list and checkbox control.
|
||||||
|
@import "../../../../../../packages/components/checkbox-list/style";
|
||||||
|
@import "../../../../../../packages/checkout/components/checkbox-control/style";
|
||||||
|
|
||||||
|
|
||||||
|
.wp-block-woocommerce-stock-filter {
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
text-transform: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-stock-filter {
|
||||||
|
&.is-loading {
|
||||||
|
@include placeholder();
|
||||||
|
margin-top: $gap;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: $gap-large;
|
||||||
|
|
||||||
|
.wc-block-stock-filter-list {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.style-dropdown {
|
||||||
|
@include includeFormTokenFieldFix();
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
gap: $gap;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.wc-block-components-filter-submit-button {
|
||||||
|
height: 36px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-blocks-components-form-token-field-wrapper {
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: unset;
|
||||||
|
width: 0;
|
||||||
|
height: max-content;
|
||||||
|
|
||||||
|
&:not(.is-loading) {
|
||||||
|
border: 1px solid $gray-700 !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-loading {
|
||||||
|
border-radius: em(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-form-token-field {
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-blocks-components-form-token-field-wrapper .components-form-token-field__input-container {
|
||||||
|
@include reset-color();
|
||||||
|
@include reset-typography();
|
||||||
|
border: 0;
|
||||||
|
padding: $gap-smaller;
|
||||||
|
border-radius: inherit;
|
||||||
|
|
||||||
|
.components-form-token-field__input {
|
||||||
|
@include font-size(small);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-form-token-field__suggestions-list {
|
||||||
|
border: 1px solid $gray-700;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: $gap-smaller;
|
||||||
|
max-height: 21em;
|
||||||
|
|
||||||
|
.components-form-token-field__suggestion {
|
||||||
|
color: $black;
|
||||||
|
border: 1px solid $gray-400;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: $gap-small;
|
||||||
|
padding: $gap-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-form-token-field__token,
|
||||||
|
.components-form-token-field__suggestion {
|
||||||
|
@include font-size(small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-product-rating {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-blocks-components-form-token-field-wrapper:not(.single-selection) .components-form-token-field__input-container {
|
||||||
|
padding: $gap-smallest 30px $gap-smallest $gap-smaller;
|
||||||
|
|
||||||
|
.components-form-token-field__token-text {
|
||||||
|
background-color: $white;
|
||||||
|
border: 1px solid;
|
||||||
|
border-right: 0;
|
||||||
|
border-radius: 25px 0 0 25px;
|
||||||
|
padding: em($gap-smallest) em($gap-smaller) em($gap-smallest) em($gap-small);
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .components-form-token-field__input {
|
||||||
|
margin: em($gap-smallest) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-button.components-form-token-field__remove-token {
|
||||||
|
background-color: $white;
|
||||||
|
border: 1px solid;
|
||||||
|
border-left: 0;
|
||||||
|
border-radius: 0 25px 25px 0;
|
||||||
|
padding: 1px em($gap-smallest) 0 0;
|
||||||
|
|
||||||
|
&.has-icon svg {
|
||||||
|
background-color: $gray-200;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-stock-filter__actions {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $gap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: $gap;
|
||||||
|
|
||||||
|
// The specificity here is needed to overwrite the margin-top that is inherited on WC block template pages such as Shop.
|
||||||
|
button[type="submit"]:not(.wp-block-search__button).wc-block-components-filter-submit-button {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
@include font-size(small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-stock-filter__button {
|
||||||
|
margin-top: em($gap-smaller);
|
||||||
|
padding: em($gap-smaller) em($gap);
|
||||||
|
@include font-size(small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-styles-wrapper .wc-block-stock-filter .wc-block-stock-filter__button {
|
||||||
|
margin-top: em($gap-smaller);
|
||||||
|
padding: em($gap-smaller) em($gap);
|
||||||
|
@include font-size(small);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { BlockEditProps } from '@wordpress/blocks';
|
||||||
|
|
||||||
|
export interface BlockProps {
|
||||||
|
className?: string;
|
||||||
|
showCounts: boolean;
|
||||||
|
isPreview?: boolean;
|
||||||
|
displayStyle: string;
|
||||||
|
selectType: string;
|
||||||
|
isEditor: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DisplayOption {
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
label: JSX.Element;
|
||||||
|
textLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Current = {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EditProps = BlockEditProps< BlockProps >;
|
|
@ -1,4 +1,10 @@
|
||||||
import { useContext, useMemo, useEffect, useLayoutEffect } from 'preact/hooks';
|
import {
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'preact/hooks';
|
||||||
import { deepSignal, peek } from 'deepsignal';
|
import { deepSignal, peek } from 'deepsignal';
|
||||||
import { useSignalEffect } from './utils';
|
import { useSignalEffect } from './utils';
|
||||||
import { directive } from './hooks';
|
import { directive } from './hooks';
|
||||||
|
@ -7,18 +13,16 @@ import { prefetch, navigate } from './router';
|
||||||
const isObject = ( item ) =>
|
const isObject = ( item ) =>
|
||||||
item && typeof item === 'object' && ! Array.isArray( item );
|
item && typeof item === 'object' && ! Array.isArray( item );
|
||||||
|
|
||||||
const mergeDeepSignals = ( target, source ) => {
|
const mergeDeepSignals = ( target, source, overwrite ) => {
|
||||||
for ( const k in source ) {
|
for ( const k in source ) {
|
||||||
if ( typeof peek( target, k ) === 'undefined' ) {
|
if ( isObject( peek( target, k ) ) && isObject( peek( source, k ) ) ) {
|
||||||
target[ `$${ k }` ] = source[ `$${ k }` ];
|
|
||||||
} else if (
|
|
||||||
isObject( peek( target, k ) ) &&
|
|
||||||
isObject( peek( source, k ) )
|
|
||||||
) {
|
|
||||||
mergeDeepSignals(
|
mergeDeepSignals(
|
||||||
target[ `$${ k }` ].peek(),
|
target[ `$${ k }` ].peek(),
|
||||||
source[ `$${ k }` ].peek()
|
source[ `$${ k }` ].peek(),
|
||||||
|
overwrite
|
||||||
);
|
);
|
||||||
|
} else if ( overwrite || typeof peek( target, k ) === 'undefined' ) {
|
||||||
|
target[ `$${ k }` ] = source[ `$${ k }` ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -29,20 +33,24 @@ export default () => {
|
||||||
'context',
|
'context',
|
||||||
( {
|
( {
|
||||||
directives: {
|
directives: {
|
||||||
context: { default: context },
|
context: { default: newContext },
|
||||||
},
|
},
|
||||||
props: { children },
|
props: { children },
|
||||||
context: inherited,
|
context: inheritedContext,
|
||||||
} ) => {
|
} ) => {
|
||||||
const { Provider } = inherited;
|
const { Provider } = inheritedContext;
|
||||||
const inheritedValue = useContext( inherited );
|
const inheritedValue = useContext( inheritedContext );
|
||||||
const value = useMemo( () => {
|
const currentValue = useRef( deepSignal( {} ) );
|
||||||
const localValue = deepSignal( context );
|
currentValue.current = useMemo( () => {
|
||||||
mergeDeepSignals( localValue, inheritedValue );
|
const newValue = deepSignal( newContext );
|
||||||
return localValue;
|
mergeDeepSignals( newValue, inheritedValue );
|
||||||
}, [ context, inheritedValue ] );
|
mergeDeepSignals( currentValue.current, newValue, true );
|
||||||
|
return currentValue.current;
|
||||||
|
}, [ newContext, inheritedValue ] );
|
||||||
|
|
||||||
return <Provider value={ value }>{ children }</Provider>;
|
return (
|
||||||
|
<Provider value={ currentValue.current }>{ children }</Provider>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{ priority: 5 }
|
{ priority: 5 }
|
||||||
);
|
);
|
||||||
|
@ -87,9 +95,17 @@ export default () => {
|
||||||
// data-wc-on--[event]
|
// data-wc-on--[event]
|
||||||
directive( 'on', ( { directives: { on }, element, evaluate, context } ) => {
|
directive( 'on', ( { directives: { on }, element, evaluate, context } ) => {
|
||||||
const contextValue = useContext( context );
|
const contextValue = useContext( context );
|
||||||
|
const events = new Map();
|
||||||
Object.entries( on ).forEach( ( [ name, path ] ) => {
|
Object.entries( on ).forEach( ( [ name, path ] ) => {
|
||||||
element.props[ `on${ name }` ] = ( event ) => {
|
const event = name.split( '--' )[ 0 ];
|
||||||
evaluate( path, { event, context: contextValue } );
|
if ( ! events.has( event ) ) events.set( event, new Set() );
|
||||||
|
events.get( event ).add( path );
|
||||||
|
} );
|
||||||
|
events.forEach( ( paths, event ) => {
|
||||||
|
element.props[ `on${ event }` ] = ( event ) => {
|
||||||
|
paths.forEach( ( path ) => {
|
||||||
|
evaluate( path, { event, context: contextValue } );
|
||||||
|
} );
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -96,6 +96,10 @@ const blocks = {
|
||||||
'collection-filters': {
|
'collection-filters': {
|
||||||
isExperimental: true,
|
isExperimental: true,
|
||||||
},
|
},
|
||||||
|
'collection-stock-filter': {
|
||||||
|
isExperimental: true,
|
||||||
|
customDir: 'collection-filters/inner-blocks/stock-filter',
|
||||||
|
},
|
||||||
'collection-price-filter': {
|
'collection-price-filter': {
|
||||||
customDir: 'collection-filters/inner-blocks/price-filter',
|
customDir: 'collection-filters/inner-blocks/price-filter',
|
||||||
isExperimental: true,
|
isExperimental: true,
|
||||||
|
@ -196,6 +200,10 @@ const entries = {
|
||||||
priceFormat: './packages/prices/index.js',
|
priceFormat: './packages/prices/index.js',
|
||||||
blocksCheckout: './packages/checkout/index.js',
|
blocksCheckout: './packages/checkout/index.js',
|
||||||
blocksComponents: './packages/components/index.ts',
|
blocksComponents: './packages/components/index.ts',
|
||||||
|
|
||||||
|
// interactivity components, exported as separate entries for now
|
||||||
|
'wc-interactivity-dropdown':
|
||||||
|
'./packages/interactivity-components/dropdown/index.ts',
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
// Shared blocks code
|
// Shared blocks code
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { store as interactivityStore } from '@woocommerce/interactivity';
|
||||||
|
|
||||||
|
export type DropdownContext = {
|
||||||
|
woocommerceDropdown: {
|
||||||
|
currentItem: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
selectedItem: {
|
||||||
|
label: string | null;
|
||||||
|
value: string | null;
|
||||||
|
};
|
||||||
|
hoveredItem: {
|
||||||
|
label: string | null;
|
||||||
|
value: string | null;
|
||||||
|
};
|
||||||
|
isOpen: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type Store = {
|
||||||
|
context: DropdownContext;
|
||||||
|
selectors: unknown;
|
||||||
|
ref: HTMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
interactivityStore( {
|
||||||
|
state: {},
|
||||||
|
selectors: {
|
||||||
|
woocommerceDropdown: {
|
||||||
|
placeholderText: ( { context }: { context: DropdownContext } ) => {
|
||||||
|
const {
|
||||||
|
woocommerceDropdown: { selectedItem },
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
return selectedItem.label || 'Select an option';
|
||||||
|
},
|
||||||
|
isSelected: ( { context }: { context: DropdownContext } ) => {
|
||||||
|
const {
|
||||||
|
woocommerceDropdown: {
|
||||||
|
currentItem: { value },
|
||||||
|
},
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
return (
|
||||||
|
context.woocommerceDropdown.selectedItem.value === value ||
|
||||||
|
context.woocommerceDropdown.hoveredItem.value === value
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
woocommerceDropdown: {
|
||||||
|
toggleIsOpen: ( store: Store ) => {
|
||||||
|
const {
|
||||||
|
context: { woocommerceDropdown },
|
||||||
|
} = store;
|
||||||
|
|
||||||
|
woocommerceDropdown.isOpen = ! woocommerceDropdown.isOpen;
|
||||||
|
},
|
||||||
|
selectDropdownItem: ( {
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
context: DropdownContext;
|
||||||
|
} ) => {
|
||||||
|
const {
|
||||||
|
woocommerceDropdown: {
|
||||||
|
currentItem: { label, value },
|
||||||
|
},
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
context.woocommerceDropdown.selectedItem = { label, value };
|
||||||
|
context.woocommerceDropdown.isOpen = false;
|
||||||
|
},
|
||||||
|
addHoverClass: ( { context }: { context: DropdownContext } ) => {
|
||||||
|
const {
|
||||||
|
woocommerceDropdown: {
|
||||||
|
currentItem: { label, value },
|
||||||
|
},
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
context.woocommerceDropdown.hoveredItem = { label, value };
|
||||||
|
},
|
||||||
|
removeHoverClass: ( { context }: { context: DropdownContext } ) => {
|
||||||
|
context.woocommerceDropdown.hoveredItem = {
|
||||||
|
label: null,
|
||||||
|
value: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} );
|
|
@ -64,6 +64,9 @@ final class AssetsController {
|
||||||
$this->api->register_script( 'wc-blocks-checkout', 'build/blocks-checkout.js', [] );
|
$this->api->register_script( 'wc-blocks-checkout', 'build/blocks-checkout.js', [] );
|
||||||
$this->api->register_script( 'wc-blocks-components', 'build/blocks-components.js', [] );
|
$this->api->register_script( 'wc-blocks-components', 'build/blocks-components.js', [] );
|
||||||
|
|
||||||
|
// Register the interactivity components here for now.
|
||||||
|
$this->api->register_script( 'wc-interactivity-dropdown', 'build/wc-interactivity-dropdown.js', [] );
|
||||||
|
|
||||||
wp_add_inline_script(
|
wp_add_inline_script(
|
||||||
'wc-blocks-middleware',
|
'wc-blocks-middleware',
|
||||||
"
|
"
|
||||||
|
|
|
@ -125,13 +125,7 @@ final class CollectionFilters extends AbstractBlock {
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( ! empty( $response['body'] ) ) {
|
if ( ! empty( $response['body'] ) ) {
|
||||||
$normalized_response = array();
|
return json_decode( wp_json_encode( $response['body'] ), true );
|
||||||
|
|
||||||
foreach ( $response['body'] as $key => $data ) {
|
|
||||||
$normalized_response[ $key ] = (array) $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $normalized_response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return array();
|
return array();
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
<?php
|
||||||
|
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CollectionStockFilter class.
|
||||||
|
*/
|
||||||
|
final class CollectionStockFilter extends AbstractBlock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block name.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $block_name = 'collection-stock-filter';
|
||||||
|
|
||||||
|
const STOCK_STATUS_QUERY_VAR = 'filter_stock_status';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra data passed through from server to client for block.
|
||||||
|
*
|
||||||
|
* @param array $stock_statuses Any stock statuses 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 $stock_statuses = [] ) {
|
||||||
|
parent::enqueue_data( $stock_statuses );
|
||||||
|
$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options(), true );
|
||||||
|
$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include and render the block.
|
||||||
|
*
|
||||||
|
* @param array $attributes Block attributes. Default empty array.
|
||||||
|
* @param string $content Block content. Default empty string.
|
||||||
|
* @param WP_Block $block Block instance.
|
||||||
|
* @return string Rendered block type output.
|
||||||
|
*/
|
||||||
|
protected function render( $attributes, $content, $block ) {
|
||||||
|
// don't render if its admin, or ajax in progress.
|
||||||
|
if ( is_admin() || wp_doing_ajax() ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stock_status_counts = $block->context['collectionData']['stock_status_counts'] ?? [];
|
||||||
|
$wrapper_attributes = get_block_wrapper_attributes();
|
||||||
|
|
||||||
|
wc_store(
|
||||||
|
array(
|
||||||
|
'state' => array(
|
||||||
|
'filters' => array(
|
||||||
|
'stockStatus' => $stock_status_counts,
|
||||||
|
'activeFilters' => '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<div %1$s>
|
||||||
|
<div class="wc-block-stock-filter__controls">%2$s</div>
|
||||||
|
<div class="wc-block-stock-filter__actions"></div>
|
||||||
|
</div>',
|
||||||
|
$wrapper_attributes,
|
||||||
|
$this->get_stock_filter_html( $stock_status_counts, $attributes ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stock filter HTML
|
||||||
|
*
|
||||||
|
* @param array $stock_counts An array of stock counts.
|
||||||
|
* @param array $attributes Block attributes. Default empty array.
|
||||||
|
* @return string Rendered block type output.
|
||||||
|
*/
|
||||||
|
private function get_stock_filter_html( $stock_counts, $attributes ) {
|
||||||
|
$display_style = $attributes['displayStyle'] ?? 'list';
|
||||||
|
$stock_statuses = wc_get_product_stock_status_options();
|
||||||
|
|
||||||
|
// check the url params to select initial item on page load.
|
||||||
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
|
||||||
|
$selected_stock_status = isset( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ) : '';
|
||||||
|
|
||||||
|
$list_items = array_map(
|
||||||
|
function( $item ) use ( $stock_statuses ) {
|
||||||
|
return array(
|
||||||
|
'label' => $stock_statuses[ $item['status'] ],
|
||||||
|
'value' => $item['status'],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
$stock_counts
|
||||||
|
);
|
||||||
|
|
||||||
|
$selected_items = array_values(
|
||||||
|
array_filter(
|
||||||
|
$list_items,
|
||||||
|
function( $item ) use ( $selected_stock_status ) {
|
||||||
|
return $item['value'] === $selected_stock_status;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Just for the dropdown, we can only select 1 item.
|
||||||
|
$selected_item = $selected_items[0] ?? array(
|
||||||
|
'label' => null,
|
||||||
|
'value' => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ( 'list' === $display_style ) : ?>
|
||||||
|
<div class="wc-block-stock-filter style-list">
|
||||||
|
<ul class="wc-block-checkbox-list wc-block-components-checkbox-list wc-block-stock-filter-list">
|
||||||
|
<?php foreach ( $stock_counts as $stock_count ) { ?>
|
||||||
|
<li>
|
||||||
|
<div class="wc-block-components-checkbox wc-block-checkbox-list__checkbox">
|
||||||
|
<label for="<?php echo esc_attr( $stock_count['status'] ); ?>">
|
||||||
|
<input
|
||||||
|
id="<?php echo esc_attr( $stock_count['status'] ); ?>"
|
||||||
|
class="wc-block-components-checkbox__input"
|
||||||
|
type="checkbox"
|
||||||
|
aria-invalid="false"
|
||||||
|
data-wc-on--change="actions.filters.updateProducts"
|
||||||
|
value="<?php echo esc_attr( $stock_count['status'] ); ?>"
|
||||||
|
<?php checked( strpos( $selected_stock_status, $stock_count['status'] ) !== false, 1 ); ?>
|
||||||
|
>
|
||||||
|
<svg class="wc-block-components-checkbox__mark" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20">
|
||||||
|
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="wc-block-components-checkbox__label">
|
||||||
|
<?php echo esc_html( $stock_statuses[ $stock_count['status'] ] ); ?>
|
||||||
|
<?php
|
||||||
|
// translators: %s: number of products.
|
||||||
|
$screen_reader_text = sprintf( _n( '%s product', '%s products', $stock_count['count'], 'woo-gutenberg-products-block' ), number_format_i18n( $stock_count['count'] ) );
|
||||||
|
?>
|
||||||
|
<span class="wc-filter-element-label-list-count">
|
||||||
|
<span aria-hidden="true">
|
||||||
|
<?php echo esc_html( $stock_count['count'] ); ?>
|
||||||
|
</span>
|
||||||
|
<span class="screen-reader-text">
|
||||||
|
<?php esc_html( $screen_reader_text ); ?>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ( 'dropdown' === $display_style ) : ?>
|
||||||
|
<?php
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dropdown::render() escapes output.
|
||||||
|
echo Dropdown::render(
|
||||||
|
array(
|
||||||
|
'items' => $list_items,
|
||||||
|
'action' => 'actions.filters.navigate',
|
||||||
|
'selected_item' => $selected_item,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
}
|
|
@ -296,6 +296,7 @@ final class BlockTypesController {
|
||||||
$block_types[] = 'ProductGalleryPager';
|
$block_types[] = 'ProductGalleryPager';
|
||||||
$block_types[] = 'ProductGalleryThumbnails';
|
$block_types[] = 'ProductGalleryThumbnails';
|
||||||
$block_types[] = 'CollectionFilters';
|
$block_types[] = 'CollectionFilters';
|
||||||
|
$block_types[] = 'CollectionStockFilter';
|
||||||
$block_types[] = 'CollectionPriceFilter';
|
$block_types[] = 'CollectionPriceFilter';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Blocks\InteractivityComponents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dropdown class. This is a component for reuse with interactivity API.
|
||||||
|
*
|
||||||
|
* @package Automattic\WooCommerce\Blocks\InteractivityComponents
|
||||||
|
*/
|
||||||
|
class Dropdown {
|
||||||
|
/**
|
||||||
|
* Render the dropdown.
|
||||||
|
*
|
||||||
|
* @param mixed $props The properties to render the dropdown with.
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
public static function render( $props ) {
|
||||||
|
wp_enqueue_script( 'wc-interactivity-dropdown' );
|
||||||
|
|
||||||
|
$selected_item = $props['selected_item'] ?? array(
|
||||||
|
'label' => null,
|
||||||
|
'value' => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
$dropdown_context = array(
|
||||||
|
'woocommerceDropdown' => array(
|
||||||
|
'selectedItem' => $selected_item,
|
||||||
|
'hoveredItem' => array(
|
||||||
|
'label' => null,
|
||||||
|
'value' => null,
|
||||||
|
),
|
||||||
|
'isOpen' => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$action = $props['action'] ?? '';
|
||||||
|
|
||||||
|
// Items should be an array of objects with a label and value property.
|
||||||
|
$items = $props['items'] ?? [];
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<div class="wc-block-stock-filter style-dropdown" data-wc-context='<?php echo wp_json_encode( $dropdown_context ); ?>' >
|
||||||
|
<div class="wc-blocks-components-form-token-field-wrapper single-selection" >
|
||||||
|
<div class="components-form-token-field" tabindex="-1">
|
||||||
|
<div class="components-form-token-field__input-container"
|
||||||
|
data-wc-class--is-active="context.woocommerceDropdown.isOpen"
|
||||||
|
tabindex="-1"
|
||||||
|
data-wc-on--click="actions.woocommerceDropdown.toggleIsOpen"
|
||||||
|
>
|
||||||
|
<input id="components-form-token-input-1" type="text" autocomplete="off" data-wc-bind--placeholder="selectors.woocommerceDropdown.placeholderText" class="components-form-token-field__input" role="combobox" aria-expanded="false" aria-autocomplete="list" aria-describedby="components-form-token-suggestions-howto-1" value="">
|
||||||
|
<ul hidden data-wc-bind--hidden="!context.woocommerceDropdown.isOpen" class="components-form-token-field__suggestions-list" id="components-form-token-suggestions-1" role="listbox">
|
||||||
|
<?php
|
||||||
|
foreach ( $items as $item ) :
|
||||||
|
$context = array(
|
||||||
|
'woocommerceDropdown' => array( 'currentItem' => $item ),
|
||||||
|
JSON_NUMERIC_CHECK,
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<li
|
||||||
|
role="option"
|
||||||
|
data-wc-on--click--select-item="actions.woocommerceDropdown.selectDropdownItem"
|
||||||
|
data-wc-on--click--parent-action="<?php echo esc_attr( $action ); ?>"
|
||||||
|
data-wc-class--is-selected="selectors.woocommerceDropdown.isSelected"
|
||||||
|
data-wc-on--mouseover="actions.woocommerceDropdown.addHoverClass"
|
||||||
|
data-wc-on--mouseout="actions.woocommerceDropdown.removeHoverClass"
|
||||||
|
data-wc-context='<?php echo wp_json_encode( $context ); ?>'
|
||||||
|
class="components-form-token-field__suggestion"
|
||||||
|
data-wc-bind--aria-selected="selectors.woocommerceDropdown.isSelected"
|
||||||
|
>
|
||||||
|
<?php echo esc_html( $item['label'] ); ?>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30" height="30" >
|
||||||
|
<path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z" ></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,9 @@
|
||||||
"@woocommerce/blocks-registry": [ "assets/js/blocks-registry" ],
|
"@woocommerce/blocks-registry": [ "assets/js/blocks-registry" ],
|
||||||
"@woocommerce/blocks-checkout": [ "packages/checkout" ],
|
"@woocommerce/blocks-checkout": [ "packages/checkout" ],
|
||||||
"@woocommerce/blocks-components": [ "packages/components" ],
|
"@woocommerce/blocks-components": [ "packages/components" ],
|
||||||
|
"@woocommerce/interactivity-components/*": [
|
||||||
|
"packages/interactivity-components/*"
|
||||||
|
],
|
||||||
"@woocommerce/price-format": [ "packages/prices" ],
|
"@woocommerce/price-format": [ "packages/prices" ],
|
||||||
"@woocommerce/block-settings": [ "assets/js/settings/blocks" ],
|
"@woocommerce/block-settings": [ "assets/js/settings/blocks" ],
|
||||||
"@woocommerce/icons": [ "assets/js/icons" ],
|
"@woocommerce/icons": [ "assets/js/icons" ],
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"./assets/js/**/*",
|
"./assets/js/**/*",
|
||||||
"./packages/checkout/**/*",
|
"./packages/checkout/**/*",
|
||||||
"./packages/components/**/*",
|
"./packages/components/**/*",
|
||||||
|
"./packages/interactivity-components/**/*",
|
||||||
"./assets/js/blocks/**/block.json",
|
"./assets/js/blocks/**/block.json",
|
||||||
"./assets/js/atomic/blocks/**/block.json",
|
"./assets/js/atomic/blocks/**/block.json",
|
||||||
"./assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/**/block.json",
|
"./assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/**/block.json",
|
||||||
|
|
Loading…
Reference in New Issue