From d4330af27108dad9f000fc4aa3c14cedad779db0 Mon Sep 17 00:00:00 2001 From: joedolson Date: Tue, 18 Mar 2025 14:41:27 +0000 Subject: [PATCH] Bundled Themes: A11y: Dismiss submenus with `esc` in Twenty Twenty. Allow submenus in Twenty Twenty to be dismissed using the `esc` key to meet WCAG 1.4.13. Set focus to previous submenu parent on `esc`, and ensure that after escaping the last submenu, tab moves to the next parent item, not back into the submenu. Props lcarevic, rcreators, pratiklondhe, poena, karmatosed, chaion07, audrasjb, mehdi01, mohonchandra, najmulsaju, saurabhdhariwal, ugyensupport, shailu25. Fixes #49950. Built from https://develop.svn.wordpress.org/trunk@60040 git-svn-id: http://core.svn.wordpress.org/trunk@59376 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- .../themes/twentytwenty/assets/js/index.js | 92 ++++++++++++++++--- wp-content/themes/twentytwenty/style-rtl.css | 4 + wp-content/themes/twentytwenty/style.css | 4 + wp-includes/version.php | 2 +- 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/wp-content/themes/twentytwenty/assets/js/index.js b/wp-content/themes/twentytwenty/assets/js/index.js index f5b142d239..258658a97e 100644 --- a/wp-content/themes/twentytwenty/assets/js/index.js +++ b/wp-content/themes/twentytwenty/assets/js/index.js @@ -438,29 +438,91 @@ twentytwenty.primaryMenu = { links = menu.getElementsByTagName( 'a' ); - // Each time a menu link is focused or blurred, toggle focus. + // Each time a menu link is focused, update focus. for ( i = 0, len = links.length; i < len; i++ ) { - links[i].addEventListener( 'focus', toggleFocus, true ); - links[i].addEventListener( 'blur', toggleFocus, true ); + links[i].addEventListener( 'focus', updateFocus, true ); } - //Sets or removes the .focus class on an element. - function toggleFocus() { + menu.addEventListener( 'focusout', removeFocus, true ); + + // Remove focus classes from menu. + function removeFocus(e){ + const leavingMenu = ! menu.contains( e.relatedTarget ); + + if ( leavingMenu ) { + // Remove focus from all li elements of primary-menu. + menu.querySelectorAll( 'li' ).forEach( function( el ) { + if ( el.classList.contains( 'focus' ) ) { + el.classList.remove( 'focus', 'closed' ); + } + }); + } + } + + // Update focus class on an element. + function updateFocus() { var self = this; - // Move up through the ancestors of the current link until we hit .primary-menu. - while ( -1 === self.className.indexOf( 'primary-menu' ) ) { - // On li elements toggle the class .focus. - if ( 'li' === self.tagName.toLowerCase() ) { - if ( -1 !== self.className.indexOf( 'focus' ) ) { - self.className = self.className.replace( ' focus', '' ); - } else { - self.className += ' focus'; - } + // Remove focus from all li elements of primary-menu. + menu.querySelectorAll( 'li' ).forEach( function( el ){ + if ( el.classList.contains( 'closed' ) ) { + el.classList.remove( 'closed' ); } - self = self.parentElement; + if ( el.classList.contains( 'focus' ) ) { + el.classList.remove( 'focus' ); + } + }); + + // Set focus on current `a` element's parent `li`. + self.parentElement.classList.add( 'focus' ); + // If current element is inside sub-menu find main parent li and add focus. + if ( self.closest( '.menu-item-has-children' ) ) { + twentytwentyFindParents( self, 'li.menu-item-has-children' ).forEach( function( element ) { + element.classList.add( 'focus' ); + }); } } + + // When the `esc` key is pressed while in menu, move focus up one level. + menu.addEventListener( 'keydown', removeFocusEsc, true ); + + // Remove focus when `esc` key pressed. + function removeFocusEsc( e ) { + e = e || window.event; + var isEscape = false, + focusedElement = e.target; + + // Find if pressed key is `esc`. + if ( 'key' in e ) { + isEscape = ( e.key === 'Escape' || e.key === 'Esc' ); + } else { + isEscape = ( e.keyCode === 27 ); + } + + // If pressed key is esc, remove focus class from parent menu li. + if ( isEscape ) { + var parentLi = focusedElement.closest( 'li' ), + nestedParent = closestExcludingSelf( parentLi, 'li.menu-item-has-children' ), + focusPosition = nestedParent ? nestedParent.querySelector('a') : false; + + if ( null !== nestedParent ) { + nestedParent.classList.add( 'focus' ); + focusPosition.focus(); + } else { + parentLi.classList.remove( 'focus' ); + parentLi.classList.add( 'closed' ); + } + } + } + + function closestExcludingSelf(element, selector) { + if ( ! element || ! selector ) { + return null; + } + const parent = element.parentElement; + + return parent ? parent.closest(selector) : null; + } } }; // twentytwenty.primaryMenu diff --git a/wp-content/themes/twentytwenty/style-rtl.css b/wp-content/themes/twentytwenty/style-rtl.css index 48f79cec2d..14e4cb486a 100644 --- a/wp-content/themes/twentytwenty/style-rtl.css +++ b/wp-content/themes/twentytwenty/style-rtl.css @@ -1676,6 +1676,10 @@ ul.primary-menu { z-index: 1; } +.primary-menu .closed ul { + display: none; +} + .primary-menu li.menu-item-has-children:hover > ul, .primary-menu li.menu-item-has-children:focus > ul, .primary-menu li.menu-item-has-children.focus > ul { diff --git a/wp-content/themes/twentytwenty/style.css b/wp-content/themes/twentytwenty/style.css index a3148aaad7..636b7821a5 100644 --- a/wp-content/themes/twentytwenty/style.css +++ b/wp-content/themes/twentytwenty/style.css @@ -1682,6 +1682,10 @@ ul.primary-menu { z-index: 1; } +.primary-menu .closed ul { + display: none; +} + .primary-menu li.menu-item-has-children:hover > ul, .primary-menu li.menu-item-has-children:focus > ul, .primary-menu li.menu-item-has-children.focus > ul { diff --git a/wp-includes/version.php b/wp-includes/version.php index 7fbab7f140..5d069389a3 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.8-beta2-60039'; +$wp_version = '6.8-beta2-60040'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.