Migrate interactivity stock filter to new store API, add improvements and bugfixes (https://github.com/woocommerce/woocommerce-blocks/pull/11827)

This commit is contained in:
Sam Seay 2023-11-27 12:26:01 +08:00 committed by GitHub
parent 590263543f
commit 1cd4df5b19
5 changed files with 217 additions and 235 deletions

View File

@ -1,10 +1,7 @@
/**
* External dependencies
*/
import {
store as interactivityStore,
navigate,
} from '@woocommerce/interactivity';
import { store, navigate, getContext } from '@woocommerce/interactivity';
import { DropdownContext } from '@woocommerce/interactivity-components/dropdown';
import { HTMLElementEvent } from '@woocommerce/types';
@ -21,59 +18,40 @@ const getUrl = ( activeFilters: string ) => {
return url.href;
};
type StockFilterState = {
filters: {
stockStatus: string;
activeFilters: string;
showDropdown: boolean;
};
};
type ActionProps = {
state: StockFilterState;
event: HTMLElementEvent< HTMLInputElement >;
};
interactivityStore( {
state: {
filters: {
stockStatus: '',
},
},
store( 'woocommerce/collection-stock-filter', {
actions: {
filters: {
navigate: ( { context }: { context: DropdownContext } ) => {
if ( context.woocommerceDropdown.selectedItem.value ) {
navigate(
getUrl( context.woocommerceDropdown.selectedItem.value )
);
// "on select" handler passed to the dropdown component.
navigate: () => {
const context = getContext< DropdownContext >(
'woocommerce/interactivity-dropdown'
);
navigate( getUrl( context.selectedItem.value || '' ) );
},
updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => {
// 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 );
}
},
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 );
}
} else {
const index = filtersArr.indexOf( event.target.value );
if ( index > -1 ) {
filtersArr.splice( index, 1 );
}
}
navigate( getUrl( filtersArr.join( ',' ) ) );
},
navigate( getUrl( filtersArr.join( ',' ) ) );
},
},
} );

View File

@ -100,9 +100,18 @@ export default () => {
// data-wc-on--[event]
directive( 'on', ( { directives: { on }, element, evaluate } ) => {
const events = new Map();
on.forEach( ( entry ) => {
element.props[ `on${ entry.suffix }` ] = ( event ) => {
evaluate( entry, event );
const event = entry.suffix.split( '--' )[ 0 ];
if ( ! events.has( event ) ) events.set( event, new Set() );
events.get( event ).add( entry );
} );
events.forEach( ( entries, event ) => {
element.props[ `on${ event }` ] = ( event ) => {
entries.forEach( ( entry ) => {
evaluate( entry, event );
} );
};
} );
} );

View File

@ -1,95 +1,93 @@
/**
* External dependencies
*/
import { store as interactivityStore } from '@woocommerce/interactivity';
import { getContext, store } 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;
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( {
store( 'woocommerce/interactivity-dropdown', {
state: {},
selectors: {
woocommerceDropdown: {
placeholderText: ( { context }: { context: DropdownContext } ) => {
const {
woocommerceDropdown: { selectedItem },
} = context;
placeholderText: () => {
const context = getContext< DropdownContext >();
const { selectedItem } = context;
return selectedItem.label || 'Select an option';
},
isSelected: ( { context }: { context: DropdownContext } ) => {
const {
woocommerceDropdown: {
currentItem: { value },
},
} = context;
return selectedItem.label || 'Select an option';
},
isSelected: () => {
const context = getContext< DropdownContext >();
return (
context.woocommerceDropdown.selectedItem.value === value ||
context.woocommerceDropdown.hoveredItem.value === value
);
},
const {
currentItem: { value },
} = context;
return (
context.selectedItem.value === value ||
context.hoveredItem.value === value
);
},
},
actions: {
woocommerceDropdown: {
toggleIsOpen: ( store: Store ) => {
const {
context: { woocommerceDropdown },
} = store;
toggleIsOpen: () => {
const context = getContext< DropdownContext >();
woocommerceDropdown.isOpen = ! woocommerceDropdown.isOpen;
},
selectDropdownItem: ( {
context,
}: {
context: DropdownContext;
} ) => {
const {
woocommerceDropdown: {
currentItem: { label, value },
},
} = context;
context.isOpen = ! context.isOpen;
},
selectDropdownItem: ( event: MouseEvent ) => {
const context = getContext< DropdownContext >();
context.woocommerceDropdown.selectedItem = { label, value };
context.woocommerceDropdown.isOpen = false;
},
addHoverClass: ( { context }: { context: DropdownContext } ) => {
const {
woocommerceDropdown: {
currentItem: { label, value },
},
} = context;
const {
currentItem: { label, value },
} = context;
context.woocommerceDropdown.hoveredItem = { label, value };
},
removeHoverClass: ( { context }: { context: DropdownContext } ) => {
context.woocommerceDropdown.hoveredItem = {
const { selectedItem } = context;
if (
selectedItem.value === value &&
selectedItem.label === label
) {
context.selectedItem = {
label: null,
value: null,
};
},
} else {
context.selectedItem = { label, value };
}
context.isOpen = false;
event.stopPropagation();
},
addHoverClass: () => {
const context = getContext< DropdownContext >();
const {
currentItem: { label, value },
} = context;
context.hoveredItem = { label, value };
},
removeHoverClass: () => {
const context = getContext< DropdownContext >();
context.hoveredItem = {
label: null,
value: null,
};
},
},
} );

View File

@ -47,17 +47,6 @@ final class CollectionStockFilter extends AbstractBlock {
$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>
@ -77,6 +66,7 @@ final class CollectionStockFilter extends AbstractBlock {
*/
private function get_stock_filter_html( $stock_counts, $attributes ) {
$display_style = $attributes['displayStyle'] ?? 'list';
$show_counts = $attributes['showCounts'] ?? false;
$stock_statuses = wc_get_product_stock_status_options();
// check the url params to select initial item on page load.
@ -84,9 +74,10 @@ final class CollectionStockFilter extends AbstractBlock {
$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 ) {
function( $item ) use ( $stock_statuses, $show_counts ) {
$label = $show_counts ? $stock_statuses[ $item['status'] ] . ' (' . $item['count'] . ')' : $stock_statuses[ $item['status'] ];
return array(
'label' => $stock_statuses[ $item['status'] ],
'label' => $label,
'value' => $item['status'],
);
},
@ -108,63 +99,70 @@ final class CollectionStockFilter extends AbstractBlock {
'value' => null,
);
$data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-stock-filter' ) );
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; ?>
<div data-wc-interactive='<?php echo esc_attr( $data_directive ); ?>'>
<?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.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 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 if ( $show_counts ) : ?>
<?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>
<span aria-hidden="true">
<?php $show_counts ? print( esc_html( '(' . $stock_count['count'] . ')' ) ) : null; ?>
</span>
<span class="screen-reader-text">
<?php esc_html( $screen_reader_text ); ?>
</span>
</span>
<?php endif; ?>
</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' => 'woocommerce/collection-stock-filter::actions.navigate',
'selected_item' => $selected_item,
)
);
?>
<?php endif; ?>
</div>
<?php
return ob_get_clean();

View File

@ -23,14 +23,12 @@ class Dropdown {
);
$dropdown_context = array(
'woocommerceDropdown' => array(
'selectedItem' => $selected_item,
'hoveredItem' => array(
'label' => null,
'value' => null,
),
'isOpen' => false,
'selectedItem' => $selected_item,
'hoveredItem' => array(
'label' => null,
'value' => null,
),
'isOpen' => false,
);
$action = $props['action'] ?? '';
@ -40,45 +38,46 @@ class Dropdown {
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"
<div data-wc-interactive='<?php echo wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-dropdown' ) ); ?>'>
<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.isOpen"
tabindex="-1"
data-wc-on--click="actions.toggleIsOpen"
>
<?php echo esc_html( $item['label'] ); ?>
</li>
<?php endforeach; ?>
</ul>
<input id="components-form-token-input-1" type="text" autocomplete="off" data-wc-bind--placeholder="selectors.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.isOpen" class="components-form-token-field__suggestions-list" id="components-form-token-suggestions-1" role="listbox">
<?php
foreach ( $items as $item ) :
$context = array(
'currentItem' => $item,
);
?>
<li
role="option"
data-wc-on--click--select-item="actions.selectDropdownItem"
data-wc-on--click--parent-action="<?php echo esc_attr( $action ); ?>"
data-wc-class--is-selected="selectors.isSelected"
data-wc-on--mouseover="actions.addHoverClass"
data-wc-on--mouseout="actions.removeHoverClass"
data-wc-context='<?php echo wp_json_encode( $context ); ?>'
class="components-form-token-field__suggestion"
data-wc-bind--aria-selected="selectors.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>
</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>
</div>
<?php
return ob_get_clean();
}