Skip to content
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

WIP: navigation block fluid breakpoint #56781

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ private static function is_responsive( $attributes ) {
return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
}

/**
* Returns whether or not the navigation has a flexible breakpoint.
*
* @param array $attributes The block attributes.
* @return bool Returns whether or not this is responsive navigation.
*/
private static function has_flexible_breakpoint( $attributes ) {
return $attributes['flexibleBreakpoint'] ? 'true' : 'false';
}

/**
* Returns whether or not a navigation has a submenu.
*
Expand Down Expand Up @@ -495,7 +505,9 @@ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks )
$is_responsive_menu = static::is_responsive( $attributes );
$style = static::get_styles( $attributes );
$class = static::get_classes( $attributes );
$wrapper_attributes = get_block_wrapper_attributes(
$has_flexible_breakpoint = static::has_flexible_breakpoint( $attributes );

$wrapper_attributes = get_block_wrapper_attributes(
array(
'class' => $class,
'style' => $style,
Expand All @@ -504,7 +516,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, $has_flexible_breakpoint );
$wrapper_attributes .= ' ' . $nav_element_directives;
}

Expand All @@ -515,25 +527,35 @@ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks )
* Get the nav element directives
*
* @param bool $should_load_view_script Whether or not the view script should be loaded.
* @param bool $has_flexible_breakpoint Whether or not the nav block will have a flexible breakpoint.
* @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, $has_flexible_breakpoint ) {
if ( ! $should_load_view_script ) {
return '';
}
// When adding to this array be mindful of security concerns.
$nav_element_context = wp_json_encode(
array(
'overlayOpenedBy' => array(),
'type' => 'overlay',
'roleAttribute' => '',
'ariaLabel' => __( 'Menu' ),
'overlayOpenedBy' => array(),
'type' => 'overlay',
'roleAttribute' => '',
'ariaLabel' => __( 'Menu' ),
'has_flexible_breakpoint' => $has_flexible_breakpoint,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be camelCase?

),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP
);

$flexible_breakpoint_directives = '';

if ( $has_flexible_breakpoint ) {
$flexible_breakpoint_directives = 'data-wp-init--overlay="actions.isNavCollapsed"';
}

return '
data-wp-interactive=\'{"namespace":"core/navigation"}\'
data-wp-context=\'' . $nav_element_context . '\'
data-wp-context=\'' . $nav_element_context . '\'
' . $flexible_breakpoint_directives . '
';
}

Expand Down
4 changes: 4 additions & 0 deletions packages/block-library/src/navigation/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
"type": "boolean",
"default": true
},
"flexibleBreakpoint": {
"type": "boolean",
"default": false
},
"openSubmenusOnClick": {
"type": "boolean",
"default": false
Expand Down
18 changes: 18 additions & 0 deletions packages/block-library/src/navigation/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ function Navigation( {
openSubmenusOnClick,
overlayMenu,
showSubmenuIcon,
flexibleBreakpoint,
templateLock,
layout: {
justifyContent,
Expand Down Expand Up @@ -522,6 +523,8 @@ function Navigation( {
`overlay-menu-preview`
);

const needsBreakpoint = isResponsive && 'mobile' === overlayMenu;

const colorGradientSettings = useMultipleOriginColorsAndGradients();
const stylingInspectorControls = (
<>
Expand Down Expand Up @@ -593,6 +596,21 @@ function Navigation( {
label={ __( 'Always' ) }
/>
</ToggleGroupControl>

{ needsBreakpoint && (
<ToggleControl
__nextHasNoMarginBottom
checked={ flexibleBreakpoint }
onChange={ ( value ) => {
setAttributes( {
flexibleBreakpoint: value,
} );
} }
disabled={ attributes.openSubmenusOnClick }
label={ __( 'Flexible breakpoint' ) }
/>
) }

{ hasSubmenus && (
<>
<h3>{ __( 'Submenus' ) }</h3>
Expand Down
19 changes: 17 additions & 2 deletions packages/block-library/src/navigation/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,17 @@ button.wp-block-navigation-item__content {
right: 0;
bottom: 0;

&:not(.hidden-by-default):not(.is-menu-open):not(.is-mobile) {
display: block;
position: static;
}

.is-mobile &:not(.hidden-by-default):not(.is-menu-open) {
display: none;
position: fixed;
}


// Low specificity so that themes can override.
& :where(.wp-block-navigation-item a) {
color: inherit;
Expand Down Expand Up @@ -611,7 +622,7 @@ button.wp-block-navigation-item__content {
}
}

@include break-small() {
/*@include break-small() {
&:not(.hidden-by-default) {
&:not(.is-menu-open) {
display: block;
Expand All @@ -632,7 +643,7 @@ button.wp-block-navigation-item__content {
left: 0;
}
}
}
}*/
}

// Default menu background and font color.
Expand Down Expand Up @@ -691,6 +702,10 @@ button.wp-block-navigation-item__content {
display: none;
}
}

.is-mobile & {
display: block !important;
}
}

// Button to close the menus.
Expand Down
102 changes: 101 additions & 1 deletion packages/block-library/src/navigation/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const focusableSelectors = [
// capture the clicks, instead of relying on the focusout event.
document.addEventListener( 'click', () => {} );

const { state, actions } = store( 'core/navigation', {
const { state, actions, callbacks } = store( 'core/navigation', {
state: {
get roleAttribute() {
const ctx = getContext();
Expand Down Expand Up @@ -176,7 +176,11 @@ const { state, actions } = store( 'core/navigation', {
ctx.lastFocusableElement =
focusableElements[ focusableElements.length - 1 ];
}
window.addEventListener( 'resize', () =>
callbacks.isNavCollapsed( ref )
);
},

focusFirstElement() {
const { ref } = getElement();
if ( state.isMenuOpen ) {
Expand All @@ -185,5 +189,101 @@ const { state, actions } = store( 'core/navigation', {
focusableElements?.[ 0 ]?.focus();
}
},

isNavCollapsed( ref ) {
// remove the is-mobile class to avoid false positives.
ref.closest( 'nav' ).classList.remove( 'is-mobile' );

// test if the nav items are wrapping before testing if the actual nav is wrapping inside its parent to avoid the recursive function if possible
if (
areItemsWrapping( ref ) === true ||
isNavWrapping( ref ) === true
) {
ref.closest( 'nav' ).classList.add( 'is-mobile' );
} else {
ref.closest( 'nav' ).classList.remove( 'is-mobile' );
}
},
},
} );

function areItemsWrapping(
wrapper,
children = wrapper.querySelectorAll( 'li' )
) {
const wrapperDimensions = wrapper.getBoundingClientRect();
//we store an array with the width of each item
const itemsWidths = getItemWidths( children );
let totalWidth = 0;
let isWrapping = false;

//the nav block may have row-gap applied, which is not calculated in getItemWidths
const computedStyle = window.getComputedStyle( wrapper );
const rowGap = parseFloat( computedStyle.rowGap ) || 0;

for ( let i = 0, len = itemsWidths.length; i < len; i++ ) {
totalWidth += itemsWidths[ i ];
if ( rowGap > 0 && i > 0 ) {
totalWidth += rowGap;
}
if ( parseInt( totalWidth ) > parseInt( wrapperDimensions.width ) ) {
isWrapping = true;
}
}
return isWrapping;
}

function isNavWrapping( ref ) {
let isWrapping = false;
//how can we check if the nav element is wrapped inside its parent if we don't know anything about it (the parent)?
//for debugging purposes
const container = ref.closest( 'nav' ); //getFlexParent( ref );
if ( container !== null ) {
const childrenWrapper = container.querySelector(
'ul.wp-block-navigation'
);
isWrapping =
childrenWrapper &&
childrenWrapper.children &&
areItemsWrapping(
container,
Array.from(
container.querySelector( 'ul.wp-block-navigation' ).children
)
);
}

return isWrapping;
}

/* I'm not sure we still need this - can we just get the nearest nav element?
function getFlexParent( elem ) {
if ( elem === document.body ) {
// Base case: Stop recursion once we go all the way to the body to avoid infinite recursion
return null;
}
const parent = elem.parentNode;
const containerStyles = window.getComputedStyle( parent );
const isFlexWrap =
containerStyles.getPropertyValue( 'flex-wrap' ) === 'wrap';
if ( isFlexWrap ) {
return parent;
}
return getFlexParent( parent );
}*/

function getItemWidths( items ) {
const itemsWidths = [];

items.forEach( function ( item ) {
const style = item.currentStyle || window.getComputedStyle( item );
const itemDimensions = item.getBoundingClientRect();
const width = parseFloat( itemDimensions.width );
const marginLeft = parseFloat( style.marginLeft );
const marginRight = parseFloat( style.marginRight );
const totalWidth = width + marginLeft + marginRight;

itemsWidths.push( totalWidth );
} );
return itemsWidths;
}
Loading