Commit e4655663de for woocommerce
commit e4655663de685747215de89a51e347de9f8cdc7d
Author: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com>
Date: Tue Nov 25 11:05:28 2025 +0100
Add to Cart with Options refactor: Pass `inputElement` as context in quantity selector (#62045)
* Store `inputElement` ref
* Remove `event` from quantity selector actions
* Remove options from `setQuantity`
* Remove default namespace
* Add changefile(s) from automation for the following project(s): woocommerce
* Remove destructuring
* Fix linting
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Sam Seay <samueljseay@gmail.com>
diff --git a/plugins/woocommerce/changelog/62045-dev-pass-input-element-as-context-in-quantity-selector b/plugins/woocommerce/changelog/62045-dev-pass-input-element-as-context-in-quantity-selector
new file mode 100644
index 0000000000..3581433b5f
--- /dev/null
+++ b/plugins/woocommerce/changelog/62045-dev-pass-input-element-as-context-in-quantity-selector
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Pass input element as context in quantity selector
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts
index c8b5e4990f..69885603a6 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts
@@ -18,6 +18,7 @@ import type { ProductDataStore } from '@woocommerce/stores/woocommerce/product-d
import { getMatchedVariation } from '../../base/utils/variations/get-matched-variation';
import { doesCartItemMatchAttributes } from '../../base/utils/variations/does-cart-item-match-attributes';
import type { GroupedProductAddToCartWithOptionsStore } from './grouped-product-selector/frontend';
+import type { Context as QuantitySelectorContext } from './quantity-selector/frontend';
import type { VariableProductAddToCartWithOptionsStore } from './variation-selector/frontend';
import type { NormalizedProductData, NormalizedVariationData } from './types';
@@ -143,11 +144,6 @@ export const getNewQuantity = (
return currentQuantity + quantity;
};
-type SetQuantityOptions = Partial< {
- changeTarget: HTMLInputElement;
- forceUpdate: boolean;
-} >;
-
export type AddToCartWithOptionsStore = {
state: {
noticeIds: string[];
@@ -160,11 +156,7 @@ export type AddToCartWithOptionsStore = {
};
actions: {
validateQuantity: ( productId: number, value?: number ) => void;
- setQuantity: (
- productId: number,
- value: number,
- options?: SetQuantityOptions
- ) => void;
+ setQuantity: ( productId: number, value: number ) => void;
addError: ( error: AddToCartError ) => string;
clearErrors: ( group?: string ) => void;
addToCart: () => void;
@@ -246,16 +238,18 @@ const { actions, state } = store<
} );
}
},
- setQuantity(
- productId: number,
- value: number,
- options: SetQuantityOptions = {}
- ) {
+ setQuantity( productId: number, value: number ) {
const context = getContext< Context >();
+ const quantitySelectorContext =
+ getContext< QuantitySelectorContext >(
+ 'woocommerce/add-to-cart-with-options-quantity-selector'
+ );
+ const inputElement = quantitySelectorContext?.inputElement;
const { products } = getConfig(
'woocommerce'
) as WooCommerceConfig;
const variations = products?.[ productId ].variations;
+ const isValueNaN = Number.isNaN( inputElement?.valueAsNumber );
if ( variations ) {
const variationIds = Object.keys( variations );
@@ -264,7 +258,7 @@ const { actions, state } = store<
const idsToUpdate = [ productId, ...variationIds ];
idsToUpdate.forEach( ( id ) => {
- if ( options?.forceUpdate ) {
+ if ( isValueNaN ) {
// Null the value first before setting the real value to ensure that
// a signal update happens.
context.quantity[ Number( id ) ] = null;
@@ -273,7 +267,7 @@ const { actions, state } = store<
context.quantity[ Number( id ) ] = value;
} );
} else {
- if ( options?.forceUpdate ) {
+ if ( isValueNaN ) {
// Null the value first before setting the real value to ensure that
// a signal update happens.
context.quantity = {
@@ -294,8 +288,8 @@ const { actions, state } = store<
actions.validateQuantity( productId, value );
}
- if ( options?.changeTarget ) {
- dispatchChangeEvent( options.changeTarget );
+ if ( inputElement ) {
+ dispatchChangeEvent( inputElement );
}
},
addError: ( error: AddToCartError ): string => {
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/quantity-selector/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/quantity-selector/frontend.ts
index eb18b719c1..10ba278fff 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/quantity-selector/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/quantity-selector/frontend.ts
@@ -3,7 +3,6 @@
*/
import { store, getContext, getElement } from '@wordpress/interactivity';
import '@woocommerce/stores/woocommerce/product-data';
-import type { HTMLElementEvent } from '@woocommerce/types';
/**
* Internal dependencies
@@ -14,6 +13,7 @@ import type { AddToCartWithOptionsStore } from '../frontend';
export type Context = {
productId: number;
allowZero?: boolean;
+ inputElement?: HTMLInputElement | null;
};
// Stores are locked to prevent 3PD usage until the API is stable.
@@ -34,18 +34,13 @@ export type QuantitySelectorStore = {
inputQuantity: number;
};
actions: {
- increaseQuantity: (
- event: HTMLElementEvent< HTMLButtonElement >
- ) => void;
- decreaseQuantity: (
- event: HTMLElementEvent< HTMLButtonElement >
- ) => void;
- handleQuantityBlur: (
- event: HTMLElementEvent< HTMLInputElement >
- ) => void;
- handleQuantityCheckboxChange: (
- event: HTMLElementEvent< HTMLInputElement >
- ) => void;
+ increaseQuantity: () => void;
+ decreaseQuantity: () => void;
+ handleQuantityBlur: () => void;
+ handleQuantityCheckboxChange: () => void;
+ };
+ callbacks: {
+ storeInputElementRef: () => void;
};
};
@@ -122,11 +117,8 @@ store< QuantitySelectorStore >(
},
},
actions: {
- increaseQuantity: (
- event: HTMLElementEvent< HTMLButtonElement >
- ) => {
- const inputElement =
- event.target.parentElement?.querySelector( '.qty' );
+ increaseQuantity: () => {
+ const { productId, inputElement } = getContext< Context >();
if ( ! ( inputElement instanceof HTMLInputElement ) ) {
return;
@@ -134,7 +126,6 @@ store< QuantitySelectorStore >(
const currentValue = Number( inputElement.value ) || 0;
- const { productId } = getContext< Context >();
const { selectedAttributes } = addToCartWithOptionsStore.state;
const productObject = getProductData(
@@ -152,22 +143,18 @@ store< QuantitySelectorStore >(
addToCartWithOptionsStore.actions.setQuantity(
productId,
- newValue,
- { changeTarget: inputElement }
+ newValue
);
},
- decreaseQuantity: (
- event: HTMLElementEvent< HTMLButtonElement >
- ) => {
- const inputElement =
- event.target.parentElement?.querySelector( '.qty' );
+ decreaseQuantity: () => {
+ const { allowZero, productId, inputElement } =
+ getContext< Context >();
if ( ! ( inputElement instanceof HTMLInputElement ) ) {
return;
}
const currentValue = Number( inputElement.value ) || 0;
- const { allowZero, productId } = getContext< Context >();
const { selectedAttributes } = addToCartWithOptionsStore.state;
const productObject = getProductData(
@@ -190,18 +177,16 @@ store< QuantitySelectorStore >(
if ( newValue !== currentValue ) {
addToCartWithOptionsStore.actions.setQuantity(
productId,
- newValue,
- { changeTarget: inputElement }
+ newValue
);
}
},
// We need to listen to blur events instead of change events because
// the change event isn't triggered in invalid numbers (ie: writing
// letters) if the current value is already invalid or an empty string.
- handleQuantityBlur: (
- event: HTMLElementEvent< HTMLInputElement >
- ) => {
- const { allowZero, productId } = getContext< Context >();
+ handleQuantityBlur: () => {
+ const { allowZero, productId, inputElement } =
+ getContext< Context >();
const { selectedAttributes } = addToCartWithOptionsStore.state;
const productObject = getProductData(
@@ -213,35 +198,28 @@ store< QuantitySelectorStore >(
return;
}
- const isValueNaN = Number.isNaN( event.target.valueAsNumber );
+ const isValueNaN = Number.isNaN( inputElement?.valueAsNumber );
const { min } = productObject;
if (
allowZero &&
- ( isValueNaN || event.target.valueAsNumber === 0 )
+ ( isValueNaN || inputElement?.valueAsNumber === 0 )
) {
addToCartWithOptionsStore.actions.setQuantity(
productId,
- 0,
- {
- changeTarget: event.target,
- forceUpdate: isValueNaN,
- }
+ 0
);
return;
}
// In other product types, we reset inputs to `min` if they are
// 0 or NaN.
- const newValue =
- ! isValueNaN && event.target.valueAsNumber > 0
- ? event.target.valueAsNumber
- : min;
+ const value = inputElement?.valueAsNumber ?? NaN;
+ const newValue = ! isNaN( value ) && value > 0 ? value : min;
addToCartWithOptionsStore.actions.setQuantity(
productId,
- newValue,
- { changeTarget: event.target, forceUpdate: isValueNaN }
+ newValue
);
},
handleQuantityCheckboxChange: () => {
@@ -259,6 +237,17 @@ store< QuantitySelectorStore >(
);
},
},
+ callbacks: {
+ storeInputElementRef: () => {
+ const { ref } = getElement();
+ if ( ref ) {
+ const context = getContext< Context >();
+ const inputElement =
+ ref.querySelector< HTMLInputElement >( '.qty' );
+ context.inputElement = inputElement;
+ }
+ },
+ },
},
{ lock: universalLock }
);
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
index bd76a4d7ba..2cf4a2470c 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
@@ -358,8 +358,7 @@ const { actions, state } = store< VariableProductAddToCartWithOptionsStore >(
) {
actions.setQuantity(
productDataState.productId,
- newValue,
- { changeTarget: ref }
+ newValue
);
}
}
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/Utils.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/Utils.php
index ffade2459a..2a8f8d9034 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/Utils.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/Utils.php
@@ -43,10 +43,10 @@ class Utils {
$pattern = '/(<input[^>]*id="quantity_[^"]*"[^>]*\/>)/';
// Replacement string to add button AFTER the matched <input> element.
/* translators: %s refers to the item name in the cart. */
- $minus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Reduce quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="woocommerce/add-to-cart-with-options-quantity-selector::actions.decreaseQuantity" data-wp-bind--disabled="woocommerce/add-to-cart-with-options-quantity-selector::!state.allowsDecrease" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">−</button>';
+ $minus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Reduce quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="actions.decreaseQuantity" data-wp-bind--disabled="!state.allowsDecrease" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">−</button>';
// Replacement string to add button AFTER the matched <input> element.
/* translators: %s refers to the item name in the cart. */
- $plus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Increase quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="woocommerce/add-to-cart-with-options-quantity-selector::actions.increaseQuantity" data-wp-bind--disabled="woocommerce/add-to-cart-with-options-quantity-selector::!state.allowsIncrease" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>';
+ $plus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Increase quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="actions.increaseQuantity" data-wp-bind--disabled="!state.allowsIncrease" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>';
$new_html = preg_replace( $pattern, $plus_button, $quantity_html );
$new_html = preg_replace( $pattern, $minus_button, $new_html );
return $new_html;
@@ -107,8 +107,8 @@ class Utils {
)
);
- $processor->set_attribute( 'data-wp-on--blur', 'woocommerce/add-to-cart-with-options-quantity-selector::actions.handleQuantityBlur' );
- $processor->set_attribute( 'data-wp-bind--value', 'woocommerce/add-to-cart-with-options-quantity-selector::state.inputQuantity' );
+ $processor->set_attribute( 'data-wp-on--blur', 'actions.handleQuantityBlur' );
+ $processor->set_attribute( 'data-wp-bind--value', 'state.inputQuantity' );
foreach ( $input_attributes as $attribute => $value ) {
$processor->set_attribute( $attribute, $value );
}
@@ -119,6 +119,7 @@ class Utils {
$wrapper_attributes = array_merge(
array(
'data-wp-interactive' => 'woocommerce/add-to-cart-with-options-quantity-selector',
+ 'data-wp-init' => 'callbacks.storeInputElementRef',
),
$wrapper_attributes
);