Interactive Price Filter: use `context` instead of `state` (#42980)
* feat: use context instead of state * fix: temporary move the context to inner element for diffing to work * fix: update context before navigation for optimistic UI
This commit is contained in:
parent
d14be998f5
commit
0c5d01a6ac
|
@ -52,7 +52,7 @@ export const PriceSlider = ( { attributes }: EditProps ) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="range">
|
||||
<div className="range-bar"></div>
|
||||
<input
|
||||
|
@ -76,6 +76,6 @@ export const PriceSlider = ( { attributes }: EditProps ) => {
|
|||
{ priceMin }
|
||||
{ priceMax }
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { store, navigate } from '@woocommerce/interactivity';
|
||||
import { store, navigate, getContext } from '@woocommerce/interactivity';
|
||||
import { formatPrice, getCurrency } from '@woocommerce/price-format';
|
||||
import { HTMLElementEvent } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { PriceFilterState } from './types';
|
||||
import type { PriceFilterContext, PriceFilterStore } from './types';
|
||||
|
||||
const getHrefWithFilters = ( state: PriceFilterState ) => {
|
||||
const { minPrice = 0, maxPrice = 0, maxRange = 0 } = state;
|
||||
const getUrl = ( context: PriceFilterContext ) => {
|
||||
const { minPrice, maxPrice, minRange, maxRange } = context;
|
||||
const url = new URL( window.location.href );
|
||||
const { searchParams } = url;
|
||||
|
||||
if ( minPrice > 0 ) {
|
||||
if ( minPrice > minRange ) {
|
||||
searchParams.set( 'min_price', minPrice.toString() );
|
||||
} else {
|
||||
searchParams.delete( 'min_price' );
|
||||
|
@ -34,80 +34,73 @@ const getHrefWithFilters = ( state: PriceFilterState ) => {
|
|||
return url.href;
|
||||
};
|
||||
|
||||
interface PriceFilterStore {
|
||||
state: PriceFilterState;
|
||||
actions: {
|
||||
setMinPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => void;
|
||||
setMaxPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => void;
|
||||
updateProducts: () => void;
|
||||
reset: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const { state } = store< PriceFilterStore >(
|
||||
'woocommerce/collection-price-filter',
|
||||
{
|
||||
store< PriceFilterStore >( 'woocommerce/collection-price-filter', {
|
||||
state: {
|
||||
get rangeStyle(): string {
|
||||
const {
|
||||
minPrice = 0,
|
||||
maxPrice = 0,
|
||||
minRange = 0,
|
||||
maxRange = 0,
|
||||
} = state;
|
||||
rangeStyle: () => {
|
||||
const { minPrice, maxPrice, minRange, maxRange } =
|
||||
getContext< PriceFilterContext >();
|
||||
|
||||
return [
|
||||
`--low: ${
|
||||
( 100 * ( minPrice - minRange ) ) /
|
||||
( maxRange - minRange )
|
||||
( 100 * ( minPrice - minRange ) ) / ( maxRange - minRange )
|
||||
}%`,
|
||||
`--high: ${
|
||||
( 100 * ( maxPrice - minRange ) ) /
|
||||
( maxRange - minRange )
|
||||
( 100 * ( maxPrice - minRange ) ) / ( maxRange - minRange )
|
||||
}%`,
|
||||
].join( ';' );
|
||||
},
|
||||
get formattedMinPrice(): string {
|
||||
const { minPrice = 0 } = state;
|
||||
formattedMinPrice: () => {
|
||||
const { minPrice } = getContext< PriceFilterContext >();
|
||||
return formatPrice( minPrice, getCurrency( { minorUnit: 0 } ) );
|
||||
},
|
||||
get formattedMaxPrice(): string {
|
||||
const { maxPrice = 0 } = state;
|
||||
formattedMaxPrice: () => {
|
||||
const { maxPrice } = getContext< PriceFilterContext >();
|
||||
return formatPrice( maxPrice, getCurrency( { minorUnit: 0 } ) );
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setMinPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => {
|
||||
const { minRange = 0, maxPrice = 0, maxRange = 0 } = state;
|
||||
updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => {
|
||||
const context = getContext< PriceFilterContext >();
|
||||
const { minRange, minPrice, maxPrice, maxRange } = context;
|
||||
const type = event.target.name;
|
||||
const value = parseFloat( event.target.value );
|
||||
state.minPrice = Math.min(
|
||||
|
||||
const currentMinPrice =
|
||||
type === 'min'
|
||||
? Math.min(
|
||||
Number.isNaN( value ) ? minRange : value,
|
||||
maxRange - 1
|
||||
);
|
||||
state.maxPrice = Math.max( maxPrice, state.minPrice + 1 );
|
||||
},
|
||||
setMaxPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => {
|
||||
const {
|
||||
minRange = 0,
|
||||
minPrice = 0,
|
||||
maxPrice = 0,
|
||||
maxRange = 0,
|
||||
} = state;
|
||||
const value = parseFloat( event.target.value );
|
||||
state.maxPrice = Math.max(
|
||||
)
|
||||
: minPrice;
|
||||
const currentMaxPrice =
|
||||
type === 'max'
|
||||
? Math.max(
|
||||
Number.isNaN( value ) ? maxRange : value,
|
||||
minRange + 1
|
||||
)
|
||||
: maxPrice;
|
||||
|
||||
context.minPrice = currentMinPrice;
|
||||
context.maxPrice = currentMaxPrice;
|
||||
|
||||
navigate(
|
||||
getUrl( {
|
||||
minRange,
|
||||
maxRange,
|
||||
minPrice: currentMinPrice,
|
||||
maxPrice: currentMaxPrice,
|
||||
} )
|
||||
);
|
||||
state.minPrice = Math.min( minPrice, maxPrice - 1 );
|
||||
},
|
||||
updateProducts: () => {
|
||||
navigate( getHrefWithFilters( state ) );
|
||||
},
|
||||
reset: () => {
|
||||
const { maxRange = 0 } = state;
|
||||
state.minPrice = 0;
|
||||
state.maxPrice = maxRange;
|
||||
navigate( getHrefWithFilters( state ) );
|
||||
navigate(
|
||||
getUrl( {
|
||||
minRange: 0,
|
||||
maxRange: 0,
|
||||
minPrice: 0,
|
||||
maxPrice: 0,
|
||||
} )
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { BlockEditProps } from '@wordpress/blocks';
|
||||
import { HTMLElementEvent } from '@woocommerce/types';
|
||||
|
||||
export type BlockAttributes = {
|
||||
showInputFields: boolean;
|
||||
|
@ -11,11 +12,26 @@ export type BlockAttributes = {
|
|||
export type EditProps = BlockEditProps< BlockAttributes >;
|
||||
|
||||
export type PriceFilterState = {
|
||||
minPrice?: number;
|
||||
maxPrice?: number;
|
||||
minRange?: number;
|
||||
maxRange?: number;
|
||||
rangeStyle: string;
|
||||
formattedMinPrice: string;
|
||||
formattedMaxPrice: string;
|
||||
rangeStyle: () => string;
|
||||
formattedMinPrice: () => string;
|
||||
formattedMaxPrice: () => string;
|
||||
};
|
||||
|
||||
export type PriceFilterContext = {
|
||||
minPrice: number;
|
||||
maxPrice: number;
|
||||
minRange: number;
|
||||
maxRange: number;
|
||||
};
|
||||
|
||||
export type FilterComponentProps = BlockEditProps< BlockAttributes > & {
|
||||
collectionData: Partial< PriceFilterState >;
|
||||
};
|
||||
|
||||
export type PriceFilterStore = {
|
||||
state: PriceFilterState;
|
||||
actions: {
|
||||
updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
Comment: Convert the Price Filter `state` to `context` to enhance its reactivity.
|
||||
|
|
@ -121,7 +121,6 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
|
||||
$price_range = $block->context['collectionData']['price_range'];
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes();
|
||||
$min_range = $price_range['min_price'] / 10 ** $price_range['currency_minor_unit'];
|
||||
$max_range = $price_range['max_price'] / 10 ** $price_range['currency_minor_unit'];
|
||||
$min_price = intval( get_query_var( self::MIN_PRICE_QUERY_VAR, $min_range ) );
|
||||
|
@ -156,12 +155,10 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
$__high = 100 * ( $max_price - $min_range ) / ( $max_range - $min_range );
|
||||
$range_style = "--low: $__low%; --high: $__high%";
|
||||
|
||||
$data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-price-filter' ) );
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $show_input_fields && $inline_input ? 'inline-input' : '',
|
||||
'data-wc-interactive' => $data_directive,
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/collection-price-filter' ) ),
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -171,8 +168,7 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
class="min"
|
||||
type="text"
|
||||
value="%d"
|
||||
data-wc-bind--value="state.minPrice"
|
||||
data-wc-on--input="actions.setMinPrice"
|
||||
data-wc-bind--value="context.minPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
/>',
|
||||
esc_attr( $min_price )
|
||||
|
@ -188,8 +184,7 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
class="max"
|
||||
type="text"
|
||||
value="%d"
|
||||
data-wc-bind--value="state.maxPrice"
|
||||
data-wc-on--input="actions.setMaxPrice"
|
||||
data-wc-bind--value="context.maxPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
/>',
|
||||
esc_attr( $max_price )
|
||||
|
@ -202,6 +197,7 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
ob_start();
|
||||
?>
|
||||
<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
|
||||
<div data-wc-context="<?php echo esc_attr( wp_json_encode( $data ) ); ?>" >
|
||||
<div
|
||||
class="range"
|
||||
style="<?php echo esc_attr( $range_style ); ?>"
|
||||
|
@ -211,25 +207,25 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
<input
|
||||
type="range"
|
||||
class="min"
|
||||
name="min"
|
||||
min="<?php echo esc_attr( $min_range ); ?>"
|
||||
max="<?php echo esc_attr( $max_range ); ?>"
|
||||
value="<?php echo esc_attr( $min_price ); ?>"
|
||||
data-wc-bind--max="state.maxRange"
|
||||
data-wc-bind--value="state.minPrice"
|
||||
data-wc-class--active="state.isMinActive"
|
||||
data-wc-on--input="actions.setMinPrice"
|
||||
data-wc-bind--min="context.minRange"
|
||||
data-wc-bind--max="context.maxRange"
|
||||
data-wc-bind--value="context.minPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
class="max"
|
||||
name="max"
|
||||
min="<?php echo esc_attr( $min_range ); ?>"
|
||||
max="<?php echo esc_attr( $max_range ); ?>"
|
||||
value="<?php echo esc_attr( $max_price ); ?>"
|
||||
data-wc-bind--max="state.maxRange"
|
||||
data-wc-bind--value="state.maxPrice"
|
||||
data-wc-class--active="state.isMaxActive"
|
||||
data-wc-on--input="actions.setMaxPrice"
|
||||
data-wc-bind--min="context.minRange"
|
||||
data-wc-bind--max="context.maxRange"
|
||||
data-wc-bind--value="context.maxPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
>
|
||||
</div>
|
||||
|
@ -239,6 +235,7 @@ final class CollectionPriceFilter extends AbstractBlock {
|
|||
<?php echo $price_max; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue