-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Navigation block: Do not toggle aria-expanded on hover when the overlay menu is opened #52170
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,7 +103,7 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes | |
) ) { | ||
// Add directives to the parent `<li>`. | ||
$w->set_attribute( 'data-wp-interactive', true ); | ||
$w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "isMenuOpen": { "click": false, "hover": false }, "overlay": false } } }' ); | ||
$w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "submenuOpenedBy": {}, "type": "submenu" } } }' ); | ||
$w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); | ||
$w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); | ||
$w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); | ||
|
@@ -711,7 +711,7 @@ function render_block_core_navigation( $attributes, $content, $block ) { | |
if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && $should_load_view_script ) { | ||
$nav_element_directives = ' | ||
data-wp-interactive | ||
data-wp-context=\'{ "core": { "navigation": { "isMenuOpen": { "click": false, "hover": false }, "overlay": true, "roleAttribute": "" } } }\' | ||
data-wp-context=\'{ "core": { "navigation": { "overlayOpenedBy": {}, "type": "overlay", "roleAttribute": "" } } }\' | ||
'; | ||
$open_button_directives = ' | ||
data-wp-on--click="actions.core.navigation.openMenuOnClick" | ||
|
@@ -736,11 +736,11 @@ function render_block_core_navigation( $attributes, $content, $block ) { | |
} | ||
|
||
$responsive_container_markup = sprintf( | ||
'<button aria-haspopup="true" %3$s class="%6$s" data-micromodal-trigger="%1$s" %11$s>%9$s</button> | ||
'<button aria-haspopup="true" %3$s class="%6$s" %11$s>%9$s</button> | ||
<div class="%5$s" style="%7$s" id="%1$s" %12$s> | ||
<div class="wp-block-navigation__responsive-close" tabindex="-1" data-micromodal-close> | ||
<div class="wp-block-navigation__responsive-close" tabindex="-1"> | ||
<div class="wp-block-navigation__responsive-dialog" aria-label="%8$s" %13$s> | ||
<button %4$s data-micromodal-close class="wp-block-navigation__responsive-container-close" %14$s>%10$s</button> | ||
<button %4$s class="wp-block-navigation__responsive-container-close" %14$s>%10$s</button> | ||
Comment on lines
-739
to
+743
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed some There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those attributes were added back in this pull request because in WordPress 6.3, without Gutenberg installed, we decided to keep the old implementation, which needs them. I assume that, as we intend to include it in the next release 🤞 , should we revert the whole pull request? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, we're pass the 6.3 feature freeze, so feel free to remove anything from the old implementation that is not needed anymore. |
||
<div class="wp-block-navigation__responsive-container-content" id="%1$s-content"> | ||
%2$s | ||
</div> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,33 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { store } from '@wordpress/interactivity'; | ||
import { store as wpStore } from '@wordpress/interactivity'; | ||
|
||
const focusableSelectors = [ | ||
'a[href]', | ||
'area[href]', | ||
'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', | ||
'select:not([disabled]):not([aria-hidden])', | ||
'textarea:not([disabled]):not([aria-hidden])', | ||
'button:not([disabled]):not([aria-hidden])', | ||
'iframe', | ||
'object', | ||
'embed', | ||
Comment on lines
-8
to
-15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the elements that are unnecessary for the menu. If in the future we need them, we can add them back. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I remember correctly, we added all the focusable elements in case other blocks, like the image block, copy and paste that logic and they need it in this case. Maybe we can abstract it somehow (in the Anyway, right now it makes sense to keep only the necessary ones 👍 |
||
'[contenteditable]', | ||
'[tabindex]:not([tabindex^="-"])', | ||
]; | ||
|
||
const openMenu = ( { context, ref }, menuOpenedOn ) => { | ||
context.core.navigation.isMenuOpen[ menuOpenedOn ] = true; | ||
const openMenu = ( store, menuOpenedOn ) => { | ||
const { context, ref, selectors } = store; | ||
selectors.core.navigation.menuOpenBy( store )[ menuOpenedOn ] = true; | ||
context.core.navigation.previousFocus = ref; | ||
if ( context.core.navigation.overlay ) { | ||
if ( context.core.navigation.type === 'overlay' ) { | ||
// Add a `has-modal-open` class to the <html> root. | ||
document.documentElement.classList.add( 'has-modal-open' ); | ||
} | ||
}; | ||
|
||
const closeMenu = ( { context, selectors }, menuClosedOn ) => { | ||
context.core.navigation.isMenuOpen[ menuClosedOn ] = false; | ||
const closeMenu = ( store, menuClosedOn ) => { | ||
const { context, selectors } = store; | ||
selectors.core.navigation.menuOpenBy( store )[ menuClosedOn ] = false; | ||
// Check if the menu is still open or not. | ||
if ( ! selectors.core.navigation.isMenuOpen( { context } ) ) { | ||
if ( ! selectors.core.navigation.isMenuOpen( store ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I started passing |
||
if ( | ||
context.core.navigation.modal?.contains( | ||
window.document.activeElement | ||
|
@@ -39,18 +37,19 @@ const closeMenu = ( { context, selectors }, menuClosedOn ) => { | |
} | ||
context.core.navigation.modal = null; | ||
context.core.navigation.previousFocus = null; | ||
if ( context.core.navigation.overlay ) { | ||
if ( context.core.navigation.type === 'overlay' ) { | ||
document.documentElement.classList.remove( 'has-modal-open' ); | ||
} | ||
} | ||
}; | ||
|
||
store( { | ||
wpStore( { | ||
Comment on lines
-48
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed this to |
||
effects: { | ||
core: { | ||
navigation: { | ||
initMenu: ( { context, selectors, ref } ) => { | ||
if ( selectors.core.navigation.isMenuOpen( { context } ) ) { | ||
initMenu: ( store ) => { | ||
const { context, selectors, ref } = store; | ||
if ( selectors.core.navigation.isMenuOpen( store ) ) { | ||
const focusableElements = | ||
ref.querySelectorAll( focusableSelectors ); | ||
context.core.navigation.modal = ref; | ||
|
@@ -60,8 +59,9 @@ store( { | |
focusableElements[ focusableElements.length - 1 ]; | ||
} | ||
}, | ||
focusFirstElement: ( { context, selectors, ref } ) => { | ||
if ( selectors.core.navigation.isMenuOpen( { context } ) ) { | ||
focusFirstElement: ( store ) => { | ||
const { selectors, ref } = store; | ||
if ( selectors.core.navigation.isMenuOpen( store ) ) { | ||
ref.querySelector( | ||
'.wp-block-navigation-item > *:first-child' | ||
).focus(); | ||
|
@@ -73,60 +73,77 @@ store( { | |
selectors: { | ||
core: { | ||
navigation: { | ||
roleAttribute: ( { context, selectors } ) => | ||
context.core.navigation.overlay && | ||
selectors.core.navigation.isMenuOpen( { context } ) | ||
roleAttribute: ( store ) => { | ||
const { context, selectors } = store; | ||
return context.core.navigation.type === 'overlay' && | ||
selectors.core.navigation.isMenuOpen( store ) | ||
? 'dialog' | ||
: '', | ||
: ''; | ||
}, | ||
isMenuOpen: ( { context } ) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I'm using |
||
// The menu is opened if either `click` or `hover` is true. | ||
Object.values( context.core.navigation.isMenuOpen ).filter( | ||
Boolean | ||
).length > 0, | ||
// The menu is opened if either `click`, `hover` or `focus` is true. | ||
Object.values( | ||
context.core.navigation[ | ||
context.core.navigation.type === 'overlay' | ||
? 'overlayOpenedBy' | ||
: 'submenuOpenedBy' | ||
] | ||
).filter( Boolean ).length > 0, | ||
menuOpenBy: ( { context } ) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above: Shouldn't we use |
||
context.core.navigation[ | ||
context.core.navigation.type === 'overlay' | ||
? 'overlayOpenedBy' | ||
: 'submenuOpenedBy' | ||
], | ||
}, | ||
}, | ||
}, | ||
actions: { | ||
core: { | ||
navigation: { | ||
openMenuOnHover( args ) { | ||
openMenu( args, 'hover' ); | ||
openMenuOnHover( store ) { | ||
const { navigation } = store.context.core; | ||
if ( | ||
navigation.type === 'submenu' && | ||
// Only open on hover if the overlay is closed. | ||
Object.values( | ||
navigation.overlayOpenedBy || {} | ||
).filter( Boolean ).length === 0 | ||
Comment on lines
+108
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the main change of this PR, where we check if the parent overlay is opened or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be safe to include a global variable to check if the overlay is opened? I can imagine other use cases where it could be handy to check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but I would only add it when there's a real need for it 🙂 |
||
) | ||
openMenu( store, 'hover' ); | ||
}, | ||
closeMenuOnHover( args ) { | ||
closeMenu( args, 'hover' ); | ||
closeMenuOnHover( store ) { | ||
closeMenu( store, 'hover' ); | ||
}, | ||
openMenuOnClick( args ) { | ||
openMenu( args, 'click' ); | ||
openMenuOnClick( store ) { | ||
openMenu( store, 'click' ); | ||
}, | ||
closeMenuOnClick( args ) { | ||
closeMenu( args, 'click' ); | ||
closeMenuOnClick( store ) { | ||
closeMenu( store, 'click' ); | ||
Comment on lines
-95
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed |
||
}, | ||
toggleMenuOnClick: ( args ) => { | ||
const { context } = args; | ||
if ( context.core.navigation.isMenuOpen.click ) { | ||
closeMenu( args, 'click' ); | ||
toggleMenuOnClick: ( store ) => { | ||
const { selectors } = store; | ||
if ( selectors.core.navigation.menuOpenBy( store ).click ) { | ||
closeMenu( store, 'click' ); | ||
} else { | ||
openMenu( args, 'click' ); | ||
openMenu( store, 'click' ); | ||
} | ||
}, | ||
handleMenuKeydown: ( args ) => { | ||
const { context, event } = args; | ||
if ( context.core.navigation.isMenuOpen.click ) { | ||
// If Escape close the menu | ||
if ( | ||
event?.key === 'Escape' || | ||
event?.keyCode === 27 | ||
) { | ||
closeMenu( args, 'click' ); | ||
handleMenuKeydown: ( store ) => { | ||
const { context, selectors, event } = store; | ||
if ( selectors.core.navigation.menuOpenBy( store ).click ) { | ||
// If Escape close the menu. | ||
if ( event?.key === 'Escape' ) { | ||
closeMenu( store, 'click' ); | ||
return; | ||
} | ||
|
||
// Trap focus if it is an overlay (main menu) | ||
// Trap focus if it is an overlay (main menu). | ||
if ( | ||
context.core.navigation.overlay && | ||
( event.key === 'Tab' || event.keyCode === 9 ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the |
||
context.core.navigation.type === 'overlay' && | ||
event.key === 'Tab' | ||
) { | ||
// If shift + tab it change the direction | ||
// If shift + tab it change the direction. | ||
if ( | ||
event.shiftKey && | ||
window.document.activeElement === | ||
|
@@ -146,21 +163,21 @@ store( { | |
} | ||
} | ||
}, | ||
handleMenuFocusout: ( args ) => { | ||
const { context, event } = args; | ||
handleMenuFocusout: ( store ) => { | ||
const { context, selectors, event } = store; | ||
// If focus is outside modal, and in the document, close menu | ||
// event.target === The element losing focus | ||
// event.relatedTarget === The element receiving focus (if any) | ||
// When focusout is outsite the document, | ||
// `window.document.activeElement` doesn't change | ||
// `window.document.activeElement` doesn't change. | ||
if ( | ||
context.core.navigation.isMenuOpen.click && | ||
selectors.core.navigation.menuOpenBy( store ).click && | ||
! context.core.navigation.modal?.contains( | ||
event.relatedTarget | ||
) && | ||
event.target !== window.document.activeElement | ||
) { | ||
closeMenu( args, 'click' ); | ||
closeMenu( store, 'click' ); | ||
} | ||
}, | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest version of
deepsignal
includes anownKey
trap, so we don't need to initialize"click"
and"hover"
anymore.