diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/frontend.ts index e715c219e71..482e3e51117 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/frontend.ts @@ -76,20 +76,88 @@ const debounceUpdate = debounce( ( context, element, event ) => { ); }, 1000 ); +const constrainRangeSliderValues = ( + /** + * Tuple containing min and max values. + */ + values: [ number, number ], + /** + * Min allowed value for the sliders. + */ + min?: number | null, + /** + * Max allowed value for the sliders. + */ + max?: number | null, + /** + * Step value for the sliders. + */ + step = 1, + /** + * Whether we're currently interacting with the min range slider or not, so we update the correct values. + */ + isMin = false +): { minPrice: number; maxPrice: number } => { + let [ minValue, maxValue ] = values; + + const isFinite = ( n: number | null | undefined ): n is number => + Number.isFinite( n ); + + if ( ! isFinite( minValue ) ) { + minValue = min || 0; + } + + if ( ! isFinite( maxValue ) ) { + maxValue = max || step; + } + + if ( isFinite( min ) && min > minValue ) { + minValue = min; + } + + if ( isFinite( max ) && max <= minValue ) { + minValue = max - step; + } + + if ( isFinite( min ) && min >= maxValue ) { + maxValue = min + step; + } + + if ( isFinite( max ) && max < maxValue ) { + maxValue = max; + } + + if ( ! isMin && minValue >= maxValue ) { + minValue = maxValue - step; + } + + if ( isMin && maxValue <= minValue ) { + maxValue = minValue + step; + } + + return { minPrice: minValue, maxPrice: maxValue }; +}; + +const getRangeStyle = ( + minPrice: number, + maxPrice: number, + minRange: number, + maxRange: number +) => { + return `--low: ${ + ( 100 * ( minPrice - minRange ) ) / ( maxRange - minRange ) + }%; --high: ${ + ( 100 * ( maxPrice - minRange ) ) / ( maxRange - minRange ) + }%;`; +}; + store< PriceFilterStore >( 'woocommerce/product-filter-price', { state: { rangeStyle: () => { const { minPrice, maxPrice, minRange, maxRange } = getContext< PriceFilterContext >(); - return [ - `--low: ${ - ( 100 * ( minPrice - minRange ) ) / ( maxRange - minRange ) - }%`, - `--high: ${ - ( 100 * ( maxPrice - minRange ) ) / ( maxRange - minRange ) - }%`, - ].join( ';' ); + return getRangeStyle( minPrice, maxPrice, minRange, maxRange ); }, formattedMinPrice: () => { const { minPrice } = getContext< PriceFilterContext >(); @@ -126,5 +194,40 @@ store< PriceFilterStore >( 'woocommerce/product-filter-price', { } ) ); }, + updateRange: ( event: HTMLElementEvent< HTMLInputElement > ) => { + const context = getContext< PriceFilterContext >(); + const { minPrice, maxPrice, minRange, maxRange } = context; + const isMin = event.target.classList.contains( 'min' ); + const targetValue = Number( event.target.value ); + const stepValue = 1; + const currentValues: [ number, number ] = isMin + ? [ + Math.round( targetValue / stepValue ) * stepValue, + maxPrice, + ] + : [ + minPrice, + Math.round( targetValue / stepValue ) * stepValue, + ]; + const values = constrainRangeSliderValues( + currentValues, + minRange, + maxRange, + stepValue, + isMin + ); + + if ( targetValue >= maxPrice ) { + context.maxPrice = minPrice + 1; + } else if ( targetValue <= minPrice ) { + context.minPrice = maxPrice - 1; + } + + if ( isMin ) { + context.minPrice = values.minPrice; + } else { + context.maxPrice = values.maxPrice; + } + }, }, } ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/types.ts index ed89d32a8e2..c08e024e8c5 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/price-filter/types.ts @@ -34,5 +34,6 @@ export type PriceFilterStore = { updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => void; selectInputContent: () => void; reset: () => void; + updateRange: ( event: HTMLElementEvent< HTMLInputElement > ) => void; }; }; diff --git a/plugins/woocommerce/changelog/50935-fix-price-filter-slider b/plugins/woocommerce/changelog/50935-fix-price-filter-slider new file mode 100644 index 00000000000..b8d0d2b926b --- /dev/null +++ b/plugins/woocommerce/changelog/50935-fix-price-filter-slider @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak +Comment: Price slider: fix range bar styling and dynamic input + diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php index 838adae31b3..97b73763b65 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php @@ -234,6 +234,7 @@ final class ProductFilterPrice extends AbstractBlock { data-wc-bind--max="context.maxRange" data-wc-bind--value="context.minPrice" data-wc-on--change="actions.updateProducts" + data-wc-on--input="actions.updateRange" >