[Accessibility] Inform screen reader users when mini cart updates (#48295)
* Create add_to_cart_success_message method in WC_Product_Simple class * Add data-success_message attribute to remove item button in cart * Add data-success_message to Add to cart button * Make screen readers announce mini cart updates * Add changelog file related to #37597 #37598 * Add translator comment to remove button data-success_message attribute * Add docblock to woocommerce_product_add_to_cart_success_message filter * Bump mini-cart template version * Refresh mini cart live region by removing it from the DOM * Add mini cart live region to DOM beforehand * Remove aria-relevant attribute before adding new content to live region * Add a delay before updating live region * Bump mini-cart template version from 9.1.0 to 9.2.0 Co-authored-by: Paulo Arromba <17236129+wavvves@users.noreply.github.com> * Bump version of woocommerce_product_add_to_cart_success_message filter Co-authored-by: Paulo Arromba <17236129+wavvves@users.noreply.github.com> * Add description to the $text param on woocommerce_product_add_to_cart_success_message filter * Refactor logic in the add_to_cart_success_message function --------- Co-authored-by: Paulo Arromba <17236129+wavvves@users.noreply.github.com>
This commit is contained in:
parent
c2fc236341
commit
2840c2f3d3
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Inform screen reader users when mini cart updates
|
|
@ -12,13 +12,14 @@ jQuery( function( $ ) {
|
||||||
this.requests = [];
|
this.requests = [];
|
||||||
this.addRequest = this.addRequest.bind( this );
|
this.addRequest = this.addRequest.bind( this );
|
||||||
this.run = this.run.bind( this );
|
this.run = this.run.bind( this );
|
||||||
|
this.$liveRegion = this.createLiveRegion();
|
||||||
|
|
||||||
$( document.body )
|
$( document.body )
|
||||||
.on( 'click', '.add_to_cart_button:not(.wc-interactive)', { addToCartHandler: this }, this.onAddToCart )
|
.on( 'click', '.add_to_cart_button:not(.wc-interactive)', { addToCartHandler: this }, this.onAddToCart )
|
||||||
.on( 'click', '.remove_from_cart_button', { addToCartHandler: this }, this.onRemoveFromCart )
|
.on( 'click', '.remove_from_cart_button', { addToCartHandler: this }, this.onRemoveFromCart )
|
||||||
.on( 'added_to_cart', this.updateButton )
|
.on( 'added_to_cart', { addToCartHandler: this }, this.onAddedToCart )
|
||||||
.on( 'ajax_request_not_sent.adding_to_cart', this.updateButton )
|
.on( 'removed_from_cart', { addToCartHandler: this }, this.onRemovedFromCart )
|
||||||
.on( 'added_to_cart removed_from_cart', { addToCartHandler: this }, this.updateFragments );
|
.on( 'ajax_request_not_sent.adding_to_cart', this.updateButton );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +66,12 @@ jQuery( function( $ ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean existing text in mini cart live region and update aria-relevant attribute
|
||||||
|
// so screen readers can identify the next update if it's the same as the previous one.
|
||||||
|
e.data.addToCartHandler.$liveRegion
|
||||||
|
.text( '' )
|
||||||
|
.removeAttr( 'aria-relevant' );
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$thisbutton.removeClass( 'added' );
|
$thisbutton.removeClass( 'added' );
|
||||||
|
@ -127,6 +134,10 @@ jQuery( function( $ ) {
|
||||||
var $thisbutton = $( this ),
|
var $thisbutton = $( this ),
|
||||||
$row = $thisbutton.closest( '.woocommerce-mini-cart-item' );
|
$row = $thisbutton.closest( '.woocommerce-mini-cart-item' );
|
||||||
|
|
||||||
|
e.data.addToCartHandler.$liveRegion
|
||||||
|
.text( '' )
|
||||||
|
.removeAttr( 'aria-relevant' );
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$row.block({
|
$row.block({
|
||||||
|
@ -207,6 +218,55 @@ jQuery( function( $ ) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update cart live region message after add/remove cart events.
|
||||||
|
*/
|
||||||
|
AddToCartHandler.prototype.alertCartUpdated = function( e, fragments, cart_hash, $button ) {
|
||||||
|
var message = $button.data( 'success_message' );
|
||||||
|
|
||||||
|
if ( !message ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the response after adding/removing an item to/from the cart is really fast,
|
||||||
|
// screen readers may not have time to identify the changes in the live region element.
|
||||||
|
// So, we add a delay to ensure an interval between messages.
|
||||||
|
e.data.addToCartHandler.$liveRegion
|
||||||
|
.delay(1000)
|
||||||
|
.text( message )
|
||||||
|
.attr( 'aria-relevant', 'all' );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add live region into the body element.
|
||||||
|
*/
|
||||||
|
AddToCartHandler.prototype.createLiveRegion = function() {
|
||||||
|
var existingLiveRegion = $( '.widget_shopping_cart_live_region' );
|
||||||
|
|
||||||
|
if ( existingLiveRegion.length ) {
|
||||||
|
return existingLiveRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $( '<div class="widget_shopping_cart_live_region screen-reader-text" role="status"></div>' ).appendTo( 'body' );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks after added to cart event.
|
||||||
|
*/
|
||||||
|
AddToCartHandler.prototype.onAddedToCart = function( e, fragments, cart_hash, $button ) {
|
||||||
|
e.data.addToCartHandler.updateButton( e, fragments, cart_hash, $button );
|
||||||
|
e.data.addToCartHandler.updateFragments( e, fragments );
|
||||||
|
e.data.addToCartHandler.alertCartUpdated( e, fragments, cart_hash, $button );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks after removed from cart event.
|
||||||
|
*/
|
||||||
|
AddToCartHandler.prototype.onRemovedFromCart = function( e, fragments, cart_hash, $button ) {
|
||||||
|
e.data.addToCartHandler.updateFragments( e, fragments );
|
||||||
|
e.data.addToCartHandler.alertCartUpdated( e, fragments, cart_hash, $button );
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init AddToCartHandler.
|
* Init AddToCartHandler.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -74,4 +74,28 @@ class WC_Product_Simple extends WC_Product {
|
||||||
|
|
||||||
return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( $text, $this->get_name() ), $this );
|
return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( $text, $this->get_name() ), $this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the add to cart button success message - used to update the mini cart live region.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function add_to_cart_success_message() {
|
||||||
|
$text = '';
|
||||||
|
|
||||||
|
if ( $this->is_purchasable() && $this->is_in_stock() ) {
|
||||||
|
/* translators: %s: Product title */
|
||||||
|
$text = __( '“%s” has been added to your cart', 'woocommerce' );
|
||||||
|
$text = sprintf( $text, $this->get_name() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter product add to cart success message.
|
||||||
|
*
|
||||||
|
* @since 9.2.0
|
||||||
|
* @param string $text The success message when a product is added to the cart.
|
||||||
|
* @param WC_Product_Simple $this Reference to the current WC_Product_Simple instance.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_product_add_to_cart_success_message', $text, $this );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1393,6 +1393,10 @@ if ( ! function_exists( 'woocommerce_template_loop_add_to_cart' ) ) {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ( is_a( $product, 'WC_Product_Simple' ) ) {
|
||||||
|
$defaults['attributes']['data-success_message'] = $product->add_to_cart_success_message();
|
||||||
|
}
|
||||||
|
|
||||||
$args = apply_filters( 'woocommerce_loop_add_to_cart_args', wp_parse_args( $args, $defaults ), $product );
|
$args = apply_filters( 'woocommerce_loop_add_to_cart_args', wp_parse_args( $args, $defaults ), $product );
|
||||||
|
|
||||||
if ( ! empty( $args['attributes']['aria-describedby'] ) ) {
|
if ( ! empty( $args['attributes']['aria-describedby'] ) ) {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*
|
*
|
||||||
* @see https://woocommerce.com/document/template-structure/
|
* @see https://woocommerce.com/document/template-structure/
|
||||||
* @package WooCommerce\Templates
|
* @package WooCommerce\Templates
|
||||||
* @version 7.9.0
|
* @version 9.2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
@ -47,13 +47,15 @@ do_action( 'woocommerce_before_mini_cart' ); ?>
|
||||||
echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
'woocommerce_cart_item_remove_link',
|
'woocommerce_cart_item_remove_link',
|
||||||
sprintf(
|
sprintf(
|
||||||
'<a href="%s" class="remove remove_from_cart_button" aria-label="%s" data-product_id="%s" data-cart_item_key="%s" data-product_sku="%s">×</a>',
|
'<a href="%s" class="remove remove_from_cart_button" aria-label="%s" data-product_id="%s" data-cart_item_key="%s" data-product_sku="%s" data-success_message="%s">×</a>',
|
||||||
esc_url( wc_get_cart_remove_url( $cart_item_key ) ),
|
esc_url( wc_get_cart_remove_url( $cart_item_key ) ),
|
||||||
/* translators: %s is the product name */
|
/* translators: %s is the product name */
|
||||||
esc_attr( sprintf( __( 'Remove %s from cart', 'woocommerce' ), wp_strip_all_tags( $product_name ) ) ),
|
esc_attr( sprintf( __( 'Remove %s from cart', 'woocommerce' ), wp_strip_all_tags( $product_name ) ) ),
|
||||||
esc_attr( $product_id ),
|
esc_attr( $product_id ),
|
||||||
esc_attr( $cart_item_key ),
|
esc_attr( $cart_item_key ),
|
||||||
esc_attr( $_product->get_sku() )
|
esc_attr( $_product->get_sku() ),
|
||||||
|
/* translators: %s is the product name */
|
||||||
|
esc_attr( sprintf( __( '“%s” has been removed from your cart', 'woocommerce' ), wp_strip_all_tags( $product_name ) ) )
|
||||||
),
|
),
|
||||||
$cart_item_key
|
$cart_item_key
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue