[Accessibility] Provide screen readers with product sorting status message (#50573)

* Update result-count template to display orderby message

* Update result count live region if products are sorted

* Bump result-count template version

* Remove additional full stop.

* Fix docblock spacings

* Remove jQuery code from sort by live region logic

* Validate filter result type

---------

Co-authored-by: Tom Cafferkey <tjcafferkey@gmail.com>
This commit is contained in:
Gabriel Manussakis 2024-09-04 04:31:08 -03:00 committed by GitHub
parent 19aac5f6fb
commit f8677c45d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 89 additions and 14 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
Provide screen readers with product sorting status message.

View File

@ -148,7 +148,14 @@ jQuery( function ( $ ) {
} );
});
document.addEventListener( 'DOMContentLoaded' , function() {
/**
* Focus on the first notice element on the page.
*
* Populated live regions don't always are announced by screen readers.
* This function focus on the first notice message with the role="alert"
* attribute to make sure it's announced.
*/
function focus_populate_live_region() {
var noticeClasses = [ 'woocommerce-message', 'woocommerce-error', 'wc-block-components-notice-banner' ];
var noticeSelectors = noticeClasses.map( function( className ) {
return '.' + className + '[role="alert"]';
@ -168,4 +175,28 @@ document.addEventListener( 'DOMContentLoaded' , function() {
firstNotice.focus();
clearTimeout( delayFocusNoticeId );
}, 500 );
} );
}
/**
* Refresh the sorted by live region.
*/
function refresh_sorted_by_live_region () {
var sorted_by_live_region = document.querySelector( '.woocommerce-result-count[data-is-sorted-by="true"]' );
if ( sorted_by_live_region ) {
var text = sorted_by_live_region.innerHTML;
var sorted_by_live_region_id = setTimeout( function() {
sorted_by_live_region.innerHTML = '';
sorted_by_live_region.innerHTML = text;
clearTimeout( sorted_by_live_region_id );
}, 1000 );
}
}
function on_document_ready() {
focus_populate_live_region();
refresh_sorted_by_live_region();
}
document.addEventListener( 'DOMContentLoaded' , on_document_ready );

View File

@ -1480,10 +1480,48 @@ if ( ! function_exists( 'woocommerce_result_count' ) ) {
if ( ! wc_get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) {
return;
}
/**
* Filters the default orderby option.
*
* @since 1.6.4
*
* @param string $default_orderby The default orderby option.
*/
$default_orderby = apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby', '' ) );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$orderby = isset( $_GET['orderby'] ) ? wc_clean( wp_unslash( $_GET['orderby'] ) ) : $default_orderby;
// If products follow the default order this doesn't need to be informed.
$orderby = 'menu_order' === $orderby ? '' : $orderby;
$orderby = is_string( $orderby ) ? $orderby : '';
/**
* Filters ordered by messages.
*
* @since 9.3.0
*
* @param array $orderedby_messages The list of messages per orderby key.
*/
$catalog_orderedby_options = apply_filters(
'woocommerce_catalog_orderedby',
array(
'menu_order' => __( 'Default sorting', 'woocommerce' ),
'popularity' => __( 'Sorted by popularity', 'woocommerce' ),
'rating' => __( 'Sorted by average rating', 'woocommerce' ),
'date' => __( 'Sorted by latest', 'woocommerce' ),
'price' => __( 'Sorted by price: low to high', 'woocommerce' ),
'price-desc' => __( 'Sorted by price: high to low', 'woocommerce' ),
)
);
$orderedby = isset( $catalog_orderedby_options[ $orderby ] ) ? $catalog_orderedby_options[ $orderby ] : '';
$orderedby = is_string( $orderedby ) ? $orderedby : '';
$args = array(
'total' => wc_get_loop_prop( 'total' ),
'per_page' => wc_get_loop_prop( 'per_page' ),
'current' => wc_get_loop_prop( 'current_page' ),
'orderedby' => $orderedby,
);
wc_get_template( 'loop/result-count.php', $args );

View File

@ -14,26 +14,28 @@
*
* @see https://woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
* @version 3.7.0
* @version 9.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<p class="woocommerce-result-count">
<p class="woocommerce-result-count" <?php echo ( empty( $orderedby ) || 1 === intval( $total ) ) ? '' : 'role="alert" aria-relevant="all" data-is-sorted-by="true"'; ?>>
<?php
// phpcs:disable WordPress.Security
if ( 1 === intval( $total ) ) {
_e( 'Showing the single result', 'woocommerce' );
} elseif ( $total <= $per_page || -1 === $per_page ) {
/* translators: %d: total results */
printf( _n( 'Showing all %d result', 'Showing all %d results', $total, 'woocommerce' ), $total );
$orderedby_placeholder = empty( $orderedby ) ? '%2$s' : '<span class="screen-reader-text">%2$s</span>';
/* translators: 1: total results 2: sorted by */
printf( _n( 'Showing all %1$d result', 'Showing all %1$d results', $total, 'woocommerce' ) . $orderedby_placeholder, $total, esc_html( $orderedby ) );
} else {
$first = ( $per_page * $current ) - $per_page + 1;
$last = min( $total, $per_page * $current );
/* translators: 1: first result 2: last result 3: total results */
printf( _nx( 'Showing %1$d&ndash;%2$d of %3$d result', 'Showing %1$d&ndash;%2$d of %3$d results', $total, 'with first and last result', 'woocommerce' ), $first, $last, $total );
$orderedby_placeholder = empty( $orderedby ) ? '%4$s' : '<span class="screen-reader-text">%4$s</span>';
/* translators: 1: first result 2: last result 3: total results 4: sorted by */
printf( _nx( 'Showing %1$d&ndash;%2$d of %3$d result', 'Showing %1$d&ndash;%2$d of %3$d results', $total, 'with first and last result', 'woocommerce' ) . $orderedby_placeholder, $first, $last, $total, esc_html( $orderedby ) );
}
// phpcs:enable WordPress.Security
?>