From f8677c45d176de1427a9f29156d9c4b5d39a031f Mon Sep 17 00:00:00 2001 From: Gabriel Manussakis <9420947+Manussakis@users.noreply.github.com> Date: Wed, 4 Sep 2024 04:31:08 -0300 Subject: [PATCH] [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 --- plugins/woocommerce/changelog/fix-43569 | 4 ++ .../client/legacy/js/frontend/woocommerce.js | 35 +++++++++++++- .../includes/wc-template-functions.php | 46 +++++++++++++++++-- .../templates/loop/result-count.php | 18 ++++---- 4 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-43569 diff --git a/plugins/woocommerce/changelog/fix-43569 b/plugins/woocommerce/changelog/fix-43569 new file mode 100644 index 00000000000..94dd68c7fdb --- /dev/null +++ b/plugins/woocommerce/changelog/fix-43569 @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Provide screen readers with product sorting status message. diff --git a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js index fd4abe3ac21..9a26837fdfd 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js +++ b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js @@ -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 ); diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php index 5187fb20817..5b804e25533 100644 --- a/plugins/woocommerce/includes/wc-template-functions.php +++ b/plugins/woocommerce/includes/wc-template-functions.php @@ -1480,10 +1480,48 @@ if ( ! function_exists( 'woocommerce_result_count' ) ) { if ( ! wc_get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { return; } - $args = array( - 'total' => wc_get_loop_prop( 'total' ), - 'per_page' => wc_get_loop_prop( 'per_page' ), - 'current' => wc_get_loop_prop( 'current_page' ), + + /** + * 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 ); diff --git a/plugins/woocommerce/templates/loop/result-count.php b/plugins/woocommerce/templates/loop/result-count.php index 1ecf28a246d..4a22073df9c 100644 --- a/plugins/woocommerce/templates/loop/result-count.php +++ b/plugins/woocommerce/templates/loop/result-count.php @@ -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; } ?> -

+

> %2$s'; + /* 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–%2$d of %3$d result', 'Showing %1$d–%2$d of %3$d results', $total, 'with first and last result', 'woocommerce' ), $first, $last, $total ); + $first = ( $per_page * $current ) - $per_page + 1; + $last = min( $total, $per_page * $current ); + $orderedby_placeholder = empty( $orderedby ) ? '%4$s' : '%4$s'; + /* translators: 1: first result 2: last result 3: total results 4: sorted by */ + printf( _nx( 'Showing %1$d–%2$d of %3$d result', 'Showing %1$d–%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 ?>