Commit 2ac4ec89d2 for woocommerce
commit 2ac4ec89d270321e8d24061fd9234e1f3d39057d
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Fri May 30 07:32:33 2025 -0600
[Experimental] Use regular form in Add to Cart with Options when iAPI can't be used in variable products (#58313)
* Use regular form in Add to Cart with Options when iAPI can't be used in variable products
* Add changelog file
* Use regular radio controls instead of div with a role attribute
* Remove Product Collection specific directive
* Update tests
* Add tests explanatory comment
* Use class names instead of generic radio input selector
* Use the attribute slug to support attribute with spaces and special characters
* Get variationId from the state instead of the context
diff --git a/plugins/woocommerce/changelog/fix-55699-add-to-cart-with-options-legacy-mode-variable-products b/plugins/woocommerce/changelog/fix-55699-add-to-cart-with-options-legacy-mode-variable-products
new file mode 100644
index 0000000000..9a2f910a83
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-55699-add-to-cart-with-options-legacy-mode-variable-products
@@ -0,0 +1,5 @@
+Significance: patch
+Type: update
+Comment: Use regular form in Add to Cart with Options when iAPI can't be used in variable products
+
+
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 11b279c3b7..19cb87e96a 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
@@ -86,12 +86,8 @@ const getMatchedVariation = (
( [ attributeName, attributeValue ] ) => {
const attributeMatched = selectedAttributes.some(
( variationAttribute ) => {
- const formattedVariationAttribute =
- 'attribute_' +
- variationAttribute.attribute.toLowerCase();
-
const isSameAttribute =
- formattedVariationAttribute === attributeName;
+ variationAttribute.attribute === attributeName;
if ( ! isSameAttribute ) {
return false;
}
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/frontend.ts
index afc154cf8e..336720ffed 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/frontend.ts
@@ -1,8 +1,8 @@
/**
* External dependencies
*/
-import type { ChangeEvent, KeyboardEvent } from 'react';
-import { store, getContext, getElement } from '@wordpress/interactivity';
+import type { ChangeEvent } from 'react';
+import { store, getContext } from '@wordpress/interactivity';
import type { CartVariationItem } from '@woocommerce/types';
/**
@@ -28,10 +28,6 @@ type Context = {
options: Option[];
};
-type PillsContext = Context & {
- focused?: string;
-};
-
// Set selected pill styles for proper contrast.
setStyles();
@@ -54,7 +50,7 @@ function setAttribute( name: string, value: string | null ) {
}
function setDefaultSelectedAttribute() {
- const context = getContext< PillsContext >();
+ const context = getContext< Context >();
setAttribute( context.name, context.selectedValue );
}
@@ -94,12 +90,8 @@ const isAttributeValueValid = ( {
return availableVariations.some( ( availableVariation ) => {
// Skip variations that don't match the current attribute value.
if (
- availableVariation.attributes[
- 'attribute_' + attributeName.toLowerCase()
- ] !== attributeValue &&
- availableVariation.attributes[
- 'attribute_' + attributeName.toLowerCase()
- ] !== '' // "" is used for "any".
+ availableVariation.attributes[ attributeName ] !== attributeValue &&
+ availableVariation.attributes[ attributeName ] !== '' // "" is used for "any".
) {
return false;
}
@@ -109,7 +101,7 @@ const isAttributeValueValid = ( {
( selectedAttribute ) => {
const availableVariationAttributeValue =
availableVariation.attributes[
- 'attribute_' + selectedAttribute.attribute.toLowerCase()
+ selectedAttribute.attribute
];
// If the current available variation matches the selected
// value, count it.
@@ -124,8 +116,7 @@ const isAttributeValueValid = ( {
// selection.
if ( availableVariationAttributeValue === '' ) {
if (
- selectedAttribute.attribute.toLowerCase() !==
- attributeName.toLowerCase() ||
+ selectedAttribute.attribute !== attributeName ||
attributeValue === selectedAttribute.value
) {
return true;
@@ -139,16 +130,16 @@ const isAttributeValueValid = ( {
} );
};
-const { state, actions } = store(
+const { state } = store(
'woocommerce/add-to-cart-with-options-variation-selector-attribute-options__pills',
{
state: {
get isPillSelected() {
- const { selectedValue, option } = getContext< PillsContext >();
+ const { selectedValue, option } = getContext< Context >();
return selectedValue === option.value;
},
get isPillDisabled() {
- const { name, option } = getContext< PillsContext >();
+ const { name, option } = getContext< Context >();
const { selectedAttributes, availableVariations } =
getContext< AddToCartWithOptionsStoreContext >(
'woocommerce/add-to-cart-with-options'
@@ -161,31 +152,8 @@ const { state, actions } = store(
availableVariations,
} );
},
- get pillTabIndex() {
- const { selectedValue, focused, option, options } =
- getContext< PillsContext >();
-
- if ( state.isPillDisabled ) {
- return -1;
- }
-
- // Allow the first pill to be focused when no option is selected.
- if (
- ! selectedValue &&
- ! focused &&
- options[ 0 ]?.value === option.value
- ) {
- return 0;
- }
-
- if ( state.isPillSelected || focused === option.value ) {
- return 0;
- }
-
- return -1;
- },
get index() {
- const context = getContext< PillsContext >();
+ const context = getContext< Context >();
return context.options.findIndex(
( option ) => option.value === context.option.value
);
@@ -196,125 +164,17 @@ const { state, actions } = store(
if ( state.isPillDisabled ) {
return;
}
- const context = getContext< PillsContext >();
+ const context = getContext< Context >();
if ( context.selectedValue === context.option.value ) {
context.selectedValue = '';
} else {
context.selectedValue = context.option.value;
}
- context.focused = context.option.value;
setAttribute( context.name, context.selectedValue );
},
- handleKeyDown( event: KeyboardEvent< HTMLElement > ) {
- let keyWasProcessed = false;
-
- switch ( event.key ) {
- case ' ':
- keyWasProcessed = true;
- actions.toggleSelected();
- break;
-
- case 'Up':
- case 'ArrowUp':
- case 'Left':
- case 'ArrowLeft': {
- keyWasProcessed = true;
- const context = getContext< PillsContext >();
- const { selectedAttributes, availableVariations } =
- getContext< AddToCartWithOptionsStoreContext >(
- 'woocommerce/add-to-cart-with-options'
- );
- const { index } = state;
- if ( index <= 0 ) {
- return;
- }
-
- for ( let i = index - 1; i >= 0; i-- ) {
- if (
- isAttributeValueValid( {
- attributeName: context.name,
- attributeValue: context.options[ i ].value,
- selectedAttributes,
- availableVariations,
- } )
- ) {
- context.selectedValue =
- context.options[ i ].value;
- context.focused = context.selectedValue;
-
- setAttribute(
- context.name,
- context.selectedValue
- );
-
- return;
- }
- }
- break;
- }
-
- case 'Down':
- case 'ArrowDown':
- case 'Right':
- case 'ArrowRight': {
- keyWasProcessed = true;
- const context = getContext< PillsContext >();
- const { selectedAttributes, availableVariations } =
- getContext< AddToCartWithOptionsStoreContext >(
- 'woocommerce/add-to-cart-with-options'
- );
- const { index } = state;
- if ( index >= context.options.length - 1 ) {
- return;
- }
-
- for (
- let i = index + 1;
- i < context.options.length;
- i++
- ) {
- if (
- isAttributeValueValid( {
- attributeName: context.name,
- attributeValue: context.options[ i ].value,
- selectedAttributes,
- availableVariations,
- } )
- ) {
- context.selectedValue =
- context.options[ i ].value;
- context.focused = context.selectedValue;
-
- setAttribute(
- context.name,
- context.selectedValue
- );
-
- return;
- }
- }
- break;
- }
- default:
- break;
- }
-
- if ( keyWasProcessed ) {
- event.stopPropagation();
- event.preventDefault();
- }
- },
},
callbacks: {
setDefaultSelectedAttribute,
- watchSelected() {
- const { focused } = getContext< PillsContext >();
-
- if ( state.pillTabIndex === 0 && focused ) {
- const { ref } = getElement();
- ref?.focus();
- }
- },
},
},
{ lock: true }
@@ -325,7 +185,7 @@ store(
{
state: {
get isOptionDisabled() {
- const { name, option } = getContext< PillsContext >();
+ const { name, option } = getContext< Context >();
if ( option.value === '' ) {
return false;
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/set-styles.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/set-styles.ts
index d434cf3533..21aa740f88 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/set-styles.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/set-styles.ts
@@ -70,7 +70,7 @@ function setStyles(): void {
// We use :where here to reduce specificity so customized colors and theme CSS take priority.
style.appendChild(
document.createTextNode(
- `:where(.wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill)[aria-checked="true"] {
+ `:where(.wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill):has(.wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input:checked) {
background-color: ${ selectedPillBackgroundColor };
color: ${ selectedPillColor };
border-color: ${ selectedPillBackgroundColor };
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/style.scss
index eeaae640de..4efd3ccf02 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/attribute-options/style.scss
@@ -24,12 +24,12 @@
);
}
- &:focus {
- outline-color: var(--wp--preset--color--accent-4);
- outline-offset: 2px;
+ &:has(.wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input:focus) {
+ outline: 1px solid var(--wp--preset--color--accent-4);
+ outline-offset: 1px;
}
- &[aria-checked="true"] {
+ &:has(.wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input:checked) {
&:hover {
background-color: color-mix(
in srgb,
@@ -39,15 +39,19 @@
}
}
- &[aria-checked="false"] {
- border-color: currentColor;
- }
-
- &[aria-disabled="true"] {
+ &:has(.wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input:disabled) {
border-color: var(--wc-subtext);
color: var(--wc-subtext);
text-decoration: line-through;
}
+
+ // We can't make it display: none, otherwise it's not focusable.
+ .wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input {
+ height: 0;
+ width: 0;
+ opacity: 0;
+ position: absolute;
+ }
}
.wc-block-add-to-cart-with-options-variation-selector-attribute-options__dropdown {
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
index 7a0c2578ba..51875ee987 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
@@ -128,18 +128,11 @@ test.describe( 'Add to Cart + Options Block', () => {
await page.goto( '/hoodie' );
- const logoNoOption = page.getByRole( 'radio', {
- name: 'No',
- exact: true,
- } );
- const colorBlueOption = page.getByRole( 'radio', {
- name: 'Blue',
- exact: true,
- } );
- const colorGreenOption = page.getByRole( 'radio', {
- name: 'Green',
- exact: true,
- } );
+ // The radio input is visually hidden and, thus, not clickable. That's
+ // why we need to select the <label> instead.
+ const logoNoOption = page.locator( 'label:has-text("No")' );
+ const colorBlueOption = page.locator( 'label:has-text("Blue")' );
+ const colorGreenOption = page.locator( 'label:has-text("Green")' );
const addToCartButton = page.getByText( 'Add to cart' ).first();
await logoNoOption.click();
@@ -172,14 +165,10 @@ test.describe( 'Add to Cart + Options Block', () => {
await page.goto( '/hoodie' );
- const logoYesOption = page.getByRole( 'radio', {
- name: 'Yes',
- exact: true,
- } );
- const colorGreenOption = page.getByRole( 'radio', {
- name: 'Green',
- exact: true,
- } );
+ // The radio input is visually hidden and, thus, not clickable. That's
+ // why we need to select the <label> instead.
+ const logoYesOption = page.locator( 'label:has-text("Yes")' );
+ const colorGreenOption = page.locator( 'label:has-text("Green")' );
await expect( colorGreenOption ).toBeEnabled();
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php
index 06aad4bb09..8d49c52641 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php
@@ -293,10 +293,11 @@ class AddToCartWithOptions extends AbstractBlock {
remove_filter( 'render_block_context', array( $this, 'set_is_descendant_of_add_to_cart_with_options_context' ) );
$wrapper_attributes = array(
- 'class' => $classes,
- 'style' => esc_attr( $classes_and_styles['styles'] ),
- 'data-wp-interactive' => 'woocommerce/add-to-cart-with-options',
- 'data-wp-context' => wp_json_encode(
+ 'class' => $classes,
+ 'style' => esc_attr( $classes_and_styles['styles'] ),
+ 'data-wp-interactive' => 'woocommerce/add-to-cart-with-options',
+ 'data-wp-class--is-invalid' => '!state.isFormValid',
+ 'data-wp-context' => wp_json_encode(
$context,
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
),
@@ -326,12 +327,15 @@ class AddToCartWithOptions extends AbstractBlock {
$hidden_input = '<input type="hidden" name="add-to-cart" value="' . $product->get_id() . '" />';
} elseif ( ProductType::GROUPED === $product_type ) {
$hidden_input = '<input type="hidden" name="add-to-cart" value="' . $product->get_id() . '" />';
+ } elseif ( ProductType::VARIABLE === $product_type ) {
+ $hidden_input = '<input type="hidden" name="add-to-cart" value="' . $product->get_id() . '" />';
+ $hidden_input .= '<input type="hidden" name="product_id" value="' . $product->get_id() . '" />';
+ $hidden_input .= '<input type="hidden" name="variation_id" data-wp-interactive="woocommerce/add-to-cart-with-options" data-wp-bind--value="state.variationId" />';
}
} else {
// Otherwise, we use the Interactivity API.
$form_attributes = array(
- 'data-wp-on--submit' => 'actions.handleSubmit',
- 'data-wp-class--is-invalid' => '!state.isFormValid',
+ 'data-wp-on--submit' => 'actions.handleSubmit',
);
}
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorAttributeOptions.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorAttributeOptions.php
index 0de2c15c04..8b7a1d2d57 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorAttributeOptions.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorAttributeOptions.php
@@ -50,9 +50,9 @@ class VariationSelectorAttributeOptions extends AbstractBlock {
return '';
}
- $attribute_name = $block->context['woocommerce/attributeName'];
+ $attribute_slug = wc_variation_attribute_name( $block->context['woocommerce/attributeName'] );
- if ( isset( $attribute_name ) ) {
+ if ( isset( $attribute_slug ) ) {
$attributes = $this->parse_attributes( $attributes );
@@ -138,30 +138,32 @@ class VariationSelectorAttributeOptions extends AbstractBlock {
*/
protected function render_pills( $attributes, $content, $block ) {
$attribute_id = $block->context['woocommerce/attributeId'];
- $attribute_name = $block->context['woocommerce/attributeName'];
+ $attribute_slug = wc_variation_attribute_name( $block->context['woocommerce/attributeName'] );
$attribute_terms = $block->context['woocommerce/attributeTerms'];
$pills = '';
foreach ( $attribute_terms as $attribute_term ) {
- $pills .= sprintf(
- '<div %s>%s</div>',
+ $input = sprintf(
+ '<input type="radio" %s/>',
$this->get_normalized_attributes(
array(
- 'role' => 'radio',
- 'class' => 'wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill',
- 'data-wp-bind--tabindex' => 'state.pillTabIndex',
- 'data-wp-bind--aria-checked' => 'state.isPillSelected',
- 'data-wp-bind--aria-disabled' => 'state.isPillDisabled',
- 'data-wp-watch' => 'callbacks.watchSelected',
- 'data-wp-on--click' => 'actions.toggleSelected',
- 'data-wp-on--keydown' => 'actions.handleKeyDown',
- 'data-wp-context' => array(
+ 'class' => 'wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input',
+ 'name' => $attribute_slug,
+ 'value' => $attribute_term['value'],
+ 'data-wp-bind--checked' => 'state.isPillSelected',
+ 'data-wp-bind--disabled' => 'state.isPillDisabled',
+ 'data-wp-watch' => 'callbacks.watchSelected',
+ 'data-wp-on--click' => 'actions.toggleSelected',
+ 'data-wp-on--keydown' => 'actions.handleKeyDown',
+ 'data-wp-context' => array(
'option' => $attribute_term,
),
),
),
$attribute_term['label']
);
+
+ $pills .= '<label class="wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill">' . $input . $attribute_term['label'] . '</label>';
}
return sprintf(
@@ -174,7 +176,7 @@ class VariationSelectorAttributeOptions extends AbstractBlock {
'aria-labeledby' => $attribute_id . '_label',
'data-wp-interactive' => $this->get_full_block_name() . '__pills',
'data-wp-context' => array(
- 'name' => $attribute_name,
+ 'name' => $attribute_slug,
'options' => $attribute_terms,
'selectedValue' => $this->get_default_selected_attribute( $attribute_terms ),
'focused' => '',
@@ -196,7 +198,7 @@ class VariationSelectorAttributeOptions extends AbstractBlock {
*/
protected function render_dropdown( $attributes, $content, $block ) {
$attribute_id = $block->context['woocommerce/attributeId'];
- $attribute_name = $block->context['woocommerce/attributeName'];
+ $attribute_slug = wc_variation_attribute_name( $block->context['woocommerce/attributeName'] );
$attribute_terms = $block->context['woocommerce/attributeTerms'];
$default_option = array(
'label' => esc_html__( 'Choose an option', 'woocommerce' ),
@@ -219,7 +221,7 @@ class VariationSelectorAttributeOptions extends AbstractBlock {
'data-wp-bind--disabled' => 'state.isOptionDisabled',
'data-wp-context' => array(
'option' => $attribute_term,
- 'name' => $attribute_name,
+ 'name' => $attribute_slug,
'options' => $attribute_terms,
),
),
@@ -236,12 +238,13 @@ class VariationSelectorAttributeOptions extends AbstractBlock {
'id' => $attribute_id,
'data-wp-interactive' => $this->get_full_block_name() . '__dropdown',
'data-wp-context' => array(
- 'name' => $attribute_name,
+ 'name' => $attribute_slug,
'options' => $attribute_terms,
'selectedValue' => $this->get_default_selected_attribute( $attribute_terms ),
),
'data-wp-init' => 'callbacks.setDefaultSelectedAttribute',
'data-wp-on--change' => 'actions.handleChange',
+ 'name' => $attribute_slug,
),
),
$options,
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorItemTemplate.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorItemTemplate.php
index 39ac42fdcf..b460eac7db 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorItemTemplate.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/VariationSelectorItemTemplate.php
@@ -48,15 +48,14 @@ class VariationSelectorItemTemplate extends AbstractBlock {
/**
* Get product row HTML.
*
- * @param string $product_attribute_name Product Attribute Name.
+ * @param string $attribute_name Product Attribute Name.
* @param array $product_attribute_terms Product Attribute Terms.
* @param WP_Block $block The Block.
* @return string Row HTML
*/
- private function get_product_row( $product_attribute_name, $product_attribute_terms, $block ): string {
+ private function get_product_row( $attribute_name, $product_attribute_terms, $block ): string {
global $product;
- $attribute_name = $product_attribute_name;
$attribute_terms = $this->get_terms( $attribute_name, $product_attribute_terms );
$product_variations = $product->get_available_variations();
@@ -66,8 +65,8 @@ class VariationSelectorItemTemplate extends AbstractBlock {
function ( $term ) use ( $product_variations, $attribute_name, $attribute_terms ) {
foreach ( $product_variations as $product_variation ) {
if (
- $term['value'] === $product_variation['attributes'][ 'attribute_' . strtolower( $attribute_name ) ] ||
- '' === $product_variation['attributes'][ 'attribute_' . strtolower( $attribute_name ) ]
+ $term['value'] === $product_variation['attributes'][ wc_variation_attribute_name( $attribute_name ) ] ||
+ '' === $product_variation['attributes'][ wc_variation_attribute_name( $attribute_name ) ]
) {
return true;
}
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php
index a84d0d2beb..50c44292e8 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php
@@ -220,7 +220,7 @@ class ProductButton extends AbstractBlock {
';
$button_directives = $is_descendant_of_add_to_cart_form ? '' : 'data-wp-on--click="actions.addCartItem"';
- $anchor_directive = 'data-wp-on--click="woocommerce/product-collection::actions.viewProduct"';
+ $anchor_directive = $is_descendant_of_add_to_cart_form ? '' : 'data-wp-on--click="woocommerce/product-collection::actions.viewProduct"';
$span_button_directives = '
data-wp-text="state.addToCartText"