diff --git a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php index ea94128e1dde29..9c270f59fa220e 100644 --- a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php +++ b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php @@ -361,16 +361,25 @@ private static function get_classes( $attributes ) { $text_decoration = $attributes['style']['typography']['textDecoration'] ?? null; $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration ); + // Sets the is-collapsed class when the navigation is set to always use the overlay. + // This saves us from needing to do this check in the view.js file (see the collapseNav function). + $is_collapsed_class = static::is_always_overlay( $attributes ) ? array( 'is-collapsed' ) : array(); + $classes = array_merge( $colors['css_classes'], $font_sizes['css_classes'], $is_responsive_menu ? array( 'is-responsive' ) : array(), $layout_class ? array( $layout_class ) : array(), - $text_decoration ? array( $text_decoration_class ) : array() + $text_decoration ? array( $text_decoration_class ) : array(), + $is_collapsed_class ); return implode( ' ', $classes ); } + private static function is_always_overlay( $attributes ) { + return isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; + } + /** * Get styles for the navigation block. * @@ -397,16 +406,12 @@ private static function get_responsive_container_markup( $attributes, $inner_blo $colors = gutenberg_block_core_navigation_build_css_colors( $attributes ); $modal_unique_id = wp_unique_id( 'modal-' ); - $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; - $responsive_container_classes = array( 'wp-block-navigation__responsive-container', - $is_hidden_by_default ? 'hidden-by-default' : '', implode( ' ', $colors['overlay_css_classes'] ), ); $open_button_classes = array( 'wp-block-navigation__responsive-container-open', - $is_hidden_by_default ? 'always-shown' : '', ); $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; @@ -504,7 +509,7 @@ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) ); if ( $is_responsive_menu ) { - $nav_element_directives = static::get_nav_element_directives( $should_load_view_script ); + $nav_element_directives = static::get_nav_element_directives( $should_load_view_script, $attributes ); $wrapper_attributes .= ' ' . $nav_element_directives; } @@ -517,12 +522,12 @@ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) * @param bool $should_load_view_script Whether or not the view script should be loaded. * @return string the directives for the navigation element. */ - private static function get_nav_element_directives( $should_load_view_script ) { + private static function get_nav_element_directives( $should_load_view_script, $attributes ) { if ( ! $should_load_view_script ) { return ''; } // When adding to this array be mindful of security concerns. - $nav_element_context = wp_json_encode( + $nav_element_context = wp_json_encode( array( 'overlayOpenedBy' => array(), 'type' => 'overlay', @@ -531,10 +536,20 @@ private static function get_nav_element_directives( $should_load_view_script ) { ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP ); - return ' + $nav_element_directives = ' data-wp-interactive=\'{"namespace":"core/navigation"}\' data-wp-context=\'' . $nav_element_context . '\' '; + + // When the navigation overlayMenu attribute is set to "always" + // we don't need to use JavaScript to collapse the menu as we set the class manually. + if ( ! static::is_always_overlay( $attributes ) ) { + $nav_element_directives .= 'data-wp-init="callbacks.initNav"'; + $nav_element_directives .= ' '; // space separator + $nav_element_directives .= 'data-wp-class--is-collapsed="context.isCollapsed"'; + } + + return $nav_element_directives; } /** diff --git a/packages/block-library/src/navigation/constants.js b/packages/block-library/src/navigation/constants.js index 21fc8bfdfb74d0..c712bc4000c36d 100644 --- a/packages/block-library/src/navigation/constants.js +++ b/packages/block-library/src/navigation/constants.js @@ -37,3 +37,5 @@ export const SELECT_NAVIGATION_MENUS_ARGS = [ 'wp_navigation', PRELOADED_NAVIGATION_MENUS_QUERY, ]; + +export const NAVIGATION_MOBILE_COLLAPSE = '600px'; diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 2e94cddcc9bc24..5589e8ea9e60f0 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -42,7 +42,7 @@ import { import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { close, Icon } from '@wordpress/icons'; -import { useInstanceId } from '@wordpress/compose'; +import { useInstanceId, useMediaQuery } from '@wordpress/compose'; /** * Internal dependencies @@ -71,6 +71,7 @@ import MenuInspectorControls from './menu-inspector-controls'; import DeletedNavigationWarning from './deleted-navigation-warning'; import AccessibleDescription from './accessible-description'; import AccessibleMenuDescription from './accessible-menu-description'; +import { NAVIGATION_MOBILE_COLLAPSE } from '../constants'; import { unlock } from '../../lock-unlock'; function Navigation( { @@ -297,6 +298,14 @@ function Navigation( { [ clientId ] ); const isResponsive = 'never' !== overlayMenu; + const isMobileBreakPoint = useMediaQuery( + `(max-width: ${ NAVIGATION_MOBILE_COLLAPSE })` + ); + + const isCollapsed = + ( 'mobile' === overlayMenu && isMobileBreakPoint ) || + 'always' === overlayMenu; + const blockProps = useBlockProps( { ref: navRef, className: classnames( @@ -310,6 +319,7 @@ function Navigation( { 'is-vertical': orientation === 'vertical', 'no-wrap': flexWrap === 'nowrap', 'is-responsive': isResponsive, + 'is-collapsed': isCollapsed, 'has-text-color': !! textColor.color || !! textColor?.class, [ getColorClassName( 'color', textColor?.slug ) ]: !! textColor?.slug, diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 107fb6e6de5fd5..eb796ae6965412 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -429,7 +429,7 @@ $color-control-label-height: 20px; // These needs extra specificity in the editor. .wp-block-navigation__responsive-container:not(.is-menu-open) { .components-button.wp-block-navigation__responsive-container-close { - @include break-small { + .is-collapsed & { display: none; } } diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 0b70ebb656cfa8..3f11c5564306cf 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -611,18 +611,19 @@ button.wp-block-navigation-item__content { } } - @include break-small() { - &:not(.hidden-by-default) { - &:not(.is-menu-open) { - display: block; - width: 100%; - position: relative; - z-index: auto; - background-color: inherit; - - .wp-block-navigation__responsive-container-close { - display: none; - } + // When the menu is collapsed, the menu button is visible. + // We are using the > selector combined with the :not(is-collapsed) selector + // as a way to target the class being added to the parent nav element. + :not(.is-collapsed) > & { + &:not(.is-menu-open) { + display: block; + width: 100%; + position: relative; + z-index: auto; + background-color: inherit; + + .wp-block-navigation__responsive-container-close { + display: none; } } @@ -686,10 +687,11 @@ button.wp-block-navigation-item__content { font-size: inherit; } - &:not(.always-shown) { - @include break-small { - display: none; - } + // When the menu is collapsed, the menu button is visible. + // We are using the > selector combined with the :not(is-collapsed) selector + // as a way to target the class being added to the parent nav element. + :not(.is-collapsed) > & { + display: none; } } diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index fb3919168a2677..d42832a1f8d02e 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -3,6 +3,11 @@ */ import { store, getContext, getElement } from '@wordpress/interactivity'; +/** + * Internal dependencies + */ +import { NAVIGATION_MOBILE_COLLAPSE } from './constants'; + const focusableSelectors = [ 'a[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', @@ -185,5 +190,26 @@ const { state, actions } = store( 'core/navigation', { focusableElements?.[ 0 ]?.focus(); } }, + initNav() { + const context = getContext(); + const mediaQuery = window.matchMedia( + `(max-width: ${ NAVIGATION_MOBILE_COLLAPSE })` + ); + + // Run once to set the initial state. + context.isCollapsed = mediaQuery.matches; + + function handleCollapse( event ) { + context.isCollapsed = event.matches; + } + + // Run on resize to update the state. + mediaQuery.addEventListener( 'change', handleCollapse ); + + // Remove the listener when the component is unmounted. + return () => { + mediaQuery.removeEventListener( 'change', handleCollapse ); + }; + }, }, } );