PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | build/scripts/block-library/navigation.php +223 -25 23.2.1 → trunk View file →
@@ -15,9 +15,9 @@
15 15 * Backward compatibility: WordPress applies default attribute values, so submenuVisibility
16 16 * will always have a value even for legacy blocks. We check the legacy openSubmenusOnClick
17 17 * attribute first to preserve original behavior for blocks saved before the migration.
18 18 *
19 - * @since 6.9.0
19 + * @since 7.0.0
20 20 *
21 21 * @param array $attributes Block attributes containing submenuVisibility and/or openSubmenusOnClick.
22 22 * @return string The visibility mode: 'hover', 'click', or 'always'.
23 23 */
@@ -40,8 +40,47 @@
40 40 return $submenu_visibility ?? 'hover';
41 41 }
42 42
43 43 /**
44 + * Returns the custom properties used by the Navigation block for a layout.
45 + *
46 + * @since 7.1.0
47 + *
48 + * @param array $layout Layout configuration.
49 + * @return array Navigation layout custom property declarations.
50 + */
51 +function gutenberg_block_core_navigation_get_layout_custom_property_declarations( $layout ) {
52 + $justification_values = array(
53 + 'left' => 'flex-start',
54 + 'center' => 'center',
55 + 'right' => 'flex-end',
56 + 'space-between' => 'space-between',
57 + );
58 + $justify_content = is_array( $layout ) ? ( $layout['justifyContent'] ?? 'left' ) : 'left';
59 + if ( ! is_string( $justify_content ) || ! isset( $justification_values[ $justify_content ] ) ) {
60 + $justify_content = 'left';
61 + }
62 +
63 + $justification = $justification_values[ $justify_content ];
64 + $is_vertical = is_array( $layout ) && 'vertical' === ( $layout['orientation'] ?? null );
65 + $align = 'center';
66 + $justify = $justification;
67 +
68 + if ( $is_vertical ) {
69 + $align = in_array( $justify_content, array( 'center', 'right' ), true ) ? $justification : 'flex-start';
70 + $justify = 'left' === $justify_content ? 'initial' : $justification;
71 + }
72 +
73 + return array(
74 + '--navigation-layout-justification-setting' => $justification,
75 + '--navigation-layout-direction' => $is_vertical ? 'column' : 'row',
76 + '--navigation-layout-wrap' => is_array( $layout ) && 'nowrap' === ( $layout['flexWrap'] ?? null ) ? 'nowrap' : 'wrap',
77 + '--navigation-layout-justify' => $justify,
78 + '--navigation-layout-align' => $align,
79 + );
80 +}
81 +
82 +/**
44 83 * Helper functions used to render the navigation block.
45 84 *
46 85 * @since 6.5.0
47 86 */
@@ -239,10 +278,16 @@
239 278 $is_list_open = false;
240 279
241 280 foreach ( $inner_blocks as $inner_block ) {
242 281 $inner_block_markup = static::get_markup_for_inner_block( $inner_block );
243 - $p = new WP_HTML_Tag_Processor( $inner_block_markup );
244 - $is_list_item = $p->next_tag( 'LI' );
282 + // Skip hidden blocks (e.g. hidden via block visibility) that render
283 + // as an empty string. Without this check, empty markup is mistaken
284 + // for a non-list-item and incorrectly closes the open <ul>.
285 + if ( '' === $inner_block_markup ) {
286 + continue;
287 + }
288 + $p = new WP_HTML_Tag_Processor( $inner_block_markup );
289 + $is_list_item = $p->next_tag( 'LI' );
245 290
246 291 if ( $is_list_item && ! $is_list_open ) {
247 292 $is_list_open = true;
248 293 $inner_blocks_html .= sprintf(
@@ -371,9 +416,9 @@
371 416 *
372 417 * @since 6.5.0
373 418 *
374 419 * @param string $overlay_template_part_id The overlay template part ID in format "theme//slug".
375 - * @param array $attributes The block attributes.
420 + * @param array $attributes The block attributes.
376 421 * @return WP_Block_List Returns the inner blocks for the overlay template part.
377 422 */
378 423 private static function get_overlay_blocks_from_template_part( $overlay_template_part_id, $attributes ) {
379 424 if ( empty( $overlay_template_part_id ) || ! is_string( $overlay_template_part_id ) ) {
@@ -472,9 +517,9 @@
472 517 *
473 518 * @since 6.5.0
474 519 *
475 520 * @param array $attributes The block attributes.
476 - * @param WP_Block $block The parsed block.
521 + * @param WP_Block $block The parsed block.
477 522 * @return WP_Block_List Returns the inner blocks for the navigation block.
478 523 */
479 524 private static function get_inner_blocks( $attributes, $block ) {
480 525 $inner_blocks = $block->inner_blocks;
@@ -575,14 +620,15 @@
575 620 'center' => 'items-justified-center',
576 621 'space-between' => 'items-justified-space-between',
577 622 );
578 623
579 - $layout_class = '';
624 + $layout_class = '';
625 + $nav_justify_content = $attributes['layout']['justifyContent'] ?? null;
580 626 if (
581 - isset( $attributes['layout']['justifyContent'] ) &&
582 - isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
627 + is_string( $nav_justify_content ) &&
628 + isset( $layout_justification[ $nav_justify_content ] )
583 629 ) {
584 - $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
630 + $layout_class .= $layout_justification[ $nav_justify_content ];
585 631 }
586 632 if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
587 633 $layout_class .= ' is-vertical';
588 634 }
@@ -642,11 +688,16 @@
642 688 *
643 689 * @since 7.0.0
644 690 *
645 691 * @param bool $is_hidden_by_default Whether the responsive menu is hidden by default.
646 - * @param bool $has_custom_overlay Whether a custom overlay is used.
647 - * @param array $colors The colors array.
692 + * @param bool $has_custom_overlay Whether a custom overlay is used.
693 + * @param array $colors The colors array.
648 694 * @return array Returns the responsive container classes.
695 + *
696 + * @phpstan-param array{
697 + * overlay_css_classes: list<string>,
698 + * ...
699 + * } $colors
649 700 */
650 701 private static function get_responsive_container_classes( $is_hidden_by_default, $has_custom_overlay, $colors ) {
651 702 $responsive_container_classes = array( 'wp-block-navigation__responsive-container' );
652 703
@@ -669,10 +720,16 @@
669 720 * Get overlay inline styles for the navigation block.
670 721 *
671 722 * @since 7.0.0
672 723 *
673 - * @param array $colors The colors array.
724 + * @param bool $has_custom_overlay Whether a custom overlay is used.
725 + * @param array $colors The colors array.
674 726 * @return string Returns the overlay inline styles.
727 + *
728 + * @phpstan-param array{
729 + * overlay_inline_styles: string,
730 + * ...
731 + * } $colors
675 732 */
676 733 private static function get_overlay_inline_styles( $has_custom_overlay, $colors ) {
677 734 $overlay_inline_styles = $has_custom_overlay ? '' : esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) );
678 735 return ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '';
@@ -682,10 +739,10 @@
682 739 * Get the responsive container markup
683 740 *
684 741 * @since 6.5.0
685 742 *
686 - * @param array $attributes The block attributes.
687 - * @param WP_Block_List $inner_blocks The list of inner blocks.
743 + * @param array $attributes The block attributes.
744 + * @param WP_Block_List $inner_blocks The list of inner blocks.
688 745 * @param string $inner_blocks_html The markup for the inner blocks.
689 746 * @return string Returns the container markup.
690 747 */
691 748 private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) {
@@ -774,8 +831,16 @@
774 831 ';
775 832 $responsive_container_content_directives = '
776 833 data-wp-watch="callbacks.focusFirstElement"
777 834 ';
835 +
836 + // The default overlay displays every submenu it contains; a custom overlay
837 + // opts out through the `disable-default-overlay` class on this container.
838 + // Submenus inherit this flag to tell the two apart, derived from the same
839 + // value as that class so the markup and the styles cannot disagree.
840 + if ( $has_custom_overlay ) {
841 + $responsive_container_directives .= ' ' . wp_interactivity_data_wp_context( array( 'hasCustomOverlay' => true ) );
842 + }
778 843 }
779 844
780 845 // Don't apply overlay inline styles if using a custom overlay template part.
781 846 // The custom overlay is responsible for its own styling.
@@ -834,10 +899,10 @@
834 899 * Get the wrapper attributes
835 900 *
836 901 * @since 6.5.0
837 902 *
838 - * @param array $attributes The block attributes.
839 - * @param WP_Block_List $inner_blocks A list of inner blocks.
903 + * @param array $attributes The block attributes.
904 + * @param WP_Block_List $inner_blocks A list of inner blocks.
840 905 * @return string Returns the navigation block markup.
841 906 */
842 907 private static function get_nav_attributes( $attributes, $inner_blocks ) {
843 908 $is_interactive = static::is_interactive( $attributes, $inner_blocks );
@@ -921,9 +986,9 @@
921 986 * Returns the markup for the navigation block.
922 987 *
923 988 * @since 6.5.0
924 989 *
925 - * @param array $attributes The block attributes.
990 + * @param array $attributes The block attributes.
926 991 * @param WP_Block_List $inner_blocks The list of inner blocks.
927 992 * @return string Returns the navigation wrapper markup.
928 993 */
929 994 private static function get_inner_block_markup( $attributes, $inner_blocks ) {
@@ -990,8 +1055,12 @@
990 1055 }
991 1056
992 1057 unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
993 1058
1059 + // Submenu detection is memoized while a navigation block renders. Reset it so
1060 + // the result of a previously rendered navigation block is not reused for this one.
1061 + static::$has_submenus = false;
1062 +
994 1063 $inner_blocks = static::get_inner_blocks( $attributes, $block );
995 1064 // Prevent navigation blocks referencing themselves from rendering.
996 1065 if ( gutenberg_block_core_navigation_block_tree_has_block_type(
997 1066 $inner_blocks,
@@ -1210,9 +1279,9 @@
1210 1279 'class_name' => 'wp-block-navigation-submenu__toggle',
1211 1280 )
1212 1281 ) ) {
1213 1282 $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' );
1214 - $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' );
1283 + $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isSubmenuOpen' );
1215 1284 // The `aria-expanded` attribute for SSR is already added in the submenu block.
1216 1285 }
1217 1286 // Add directives to the submenu.
1218 1287 if ( $tags->next_tag(
@@ -1557,8 +1626,137 @@
1557 1626
1558 1627 add_action( 'init', 'gutenberg_register_block_core_navigation', 20 );
1559 1628
1560 1629 /**
1630 + * Adds Navigation block support classes to inner list containers.
1631 + *
1632 + * State block support adds the generated `wp-states-*` class to the outer
1633 + * block wrapper. The Navigation block renders its menu items inside an inner
1634 + * `wp-block-navigation__container` list, so the same state class is also needed
1635 + * there for state styles to apply directly to the menu list.
1636 + *
1637 + * Navigation also uses layout classes on its outer wrapper to define custom
1638 + * properties consumed by its inner containers. Viewport layout styles cannot
1639 + * change those classes, so equivalent custom properties and a scoping class
1640 + * are generated for each configured viewport layout.
1641 + *
1642 + * Currently this is required as a workaround because of how difficult it is for nav
1643 + * child blocks to inherit styles through the complex responsive nav block html. The
1644 + * bug in https://github.com/WordPress/gutenberg/issues/62690 also prevents inheritance.
1645 + *
1646 + * @since 7.1.0
1647 + *
1648 + * @param string $block_content The block content.
1649 + * @param array $block The full block, including name and attributes.
1650 + * @return string The updated block content.
1651 + */
1652 +function gutenberg_block_core_navigation_add_support_classes_to_container( $block_content, $block ) {
1653 + if ( 'core/navigation' !== ( $block['blockName'] ?? null ) || empty( $block_content ) ) {
1654 + return $block_content;
1655 + }
1656 +
1657 + $attributes = is_array( $block['attrs'] ?? null ) ? $block['attrs'] : array();
1658 + $style = is_array( $attributes['style'] ?? null ) ? $attributes['style'] : array();
1659 + if (
1660 + defined( 'IS_GUTENBERG_PLUGIN' ) &&
1661 + IS_GUTENBERG_PLUGIN &&
1662 + function_exists( 'gutenberg_resolve_style_state_aliases' )
1663 + ) {
1664 + $style = gutenberg_resolve_style_state_aliases( $style, 'core/navigation' );
1665 + }
1666 +
1667 + $global_settings = gutenberg_get_global_settings();
1668 + $viewport_settings = $global_settings['viewport'] ?? null;
1669 + $responsive_media_queries = array();
1670 + if ( method_exists( 'WP_Theme_JSON_Gutenberg', 'get_viewport_media_queries' ) ) {
1671 + $responsive_media_queries = WP_Theme_JSON_Gutenberg::get_viewport_media_queries( $viewport_settings );
1672 + } elseif ( method_exists( 'WP_Theme_JSON', 'get_viewport_media_queries' ) ) {
1673 + $responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );
1674 + }
1675 +
1676 + $styles = array();
1677 + $base_layout = is_array( $attributes['layout'] ?? null ) ? $attributes['layout'] : array();
1678 + foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
1679 + $viewport_style = is_array( $style[ $breakpoint ] ?? null ) ? $style[ $breakpoint ] : array();
1680 + $viewport_layout = is_array( $viewport_style['layout'] ?? null ) ? $viewport_style['layout'] : array();
1681 + if ( empty( $viewport_layout ) ) {
1682 + continue;
1683 + }
1684 +
1685 + $styles[] = array(
1686 + 'declarations' => gutenberg_block_core_navigation_get_layout_custom_property_declarations(
1687 + array_replace( $base_layout, $viewport_layout )
1688 + ),
1689 + 'rules_group' => $media_query,
1690 + );
1691 + }
1692 +
1693 + $processor = new WP_HTML_Tag_Processor( $block_content );
1694 + if ( ! $processor->next_tag() ) {
1695 + return $block_content;
1696 + }
1697 +
1698 + $class_attribute = $processor->get_attribute( 'class' );
1699 + $state_class = null;
1700 + if ( is_string( $class_attribute ) && preg_match( '/\bwp-states-[a-f0-9]{8}\b/', $class_attribute, $matches ) ) {
1701 + $state_class = $matches[0];
1702 + }
1703 +
1704 + $layout_class = null;
1705 + if ( ! empty( $styles ) ) {
1706 + $layout_class = wp_unique_id( 'wp-block-navigation-' );
1707 + // The inner selector includes both Navigation classes so it overrides the
1708 + // default layout custom properties set by `.wp-block-navigation.items-*`.
1709 + $selector = ".wp-block-navigation.{$layout_class},.wp-block-navigation.wp-block-navigation__container.{$layout_class}";
1710 + foreach ( $styles as &$style_rule ) {
1711 + $style_rule['selector'] = $selector;
1712 + }
1713 + unset( $style_rule );
1714 +
1715 + $processor->add_class( $layout_class );
1716 + gutenberg_style_engine_get_stylesheet_from_css_rules(
1717 + $styles,
1718 + array( 'context' => 'block-supports' )
1719 + );
1720 + }
1721 +
1722 + if ( null === $state_class && null === $layout_class ) {
1723 + return $block_content;
1724 + }
1725 +
1726 + while ( $processor->next_tag() ) {
1727 + // Custom overlay content can include nested Navigation blocks.
1728 + // Avoid applying the outer Navigation classes to an inner nav block.
1729 + if ( $processor->has_class( 'wp-block-navigation' ) && ! $processor->has_class( 'wp-block-navigation__container' ) ) {
1730 + break;
1731 + }
1732 +
1733 + if ( ! $processor->has_class( 'wp-block-navigation__container' ) ) {
1734 + continue;
1735 + }
1736 +
1737 + if ( null !== $layout_class ) {
1738 + $processor->add_class( $layout_class );
1739 + }
1740 +
1741 + if ( null === $state_class ) {
1742 + continue;
1743 + }
1744 +
1745 + $class_attribute = $processor->get_attribute( 'class' );
1746 + if ( is_string( $class_attribute ) && preg_match( '/\bwp-states-[a-f0-9]{8}\b/', $class_attribute ) ) {
1747 + continue;
1748 + }
1749 +
1750 + $processor->add_class( $state_class );
1751 + }
1752 +
1753 + return $processor->get_updated_html();
1754 +}
1755 +
1756 +add_filter( 'render_block', 'gutenberg_block_core_navigation_add_support_classes_to_container', 11, 2 );
1757 +
1758 +/**
1561 1759 * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
1562 1760 *
1563 1761 * @since 5.9.0
1564 1762 *
@@ -1599,14 +1797,14 @@
1599 1797 * @since 5.9.0
1600 1798 *
1601 1799 * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead.
1602 1800 *
1603 - * @param array $menu_items An array of menu items that represent
1604 - * an individual level of a menu.
1605 - * @param array $menu_items_by_parent_id An array keyed by the id of the
1606 - * parent menu where each element is an
1607 - * array of menu items that belong to
1608 - * that parent.
1801 + * @param array $menu_items An array of menu items that represent
1802 + * an individual level of a menu.
1803 + * @param array $menu_items_by_parent_id An array keyed by the id of the
1804 + * parent menu where each element is an
1805 + * array of menu items that belong to
1806 + * that parent.
1609 1807 * @return array An array of parsed block data.
1610 1808 */
1611 1809 function gutenberg_block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
1612 1810
@@ -1704,9 +1902,9 @@
1704 1902 * @since 6.2.0
1705 1903 *
1706 1904 * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead.
1707 1905 *
1708 - * @param object $classic_nav_menu WP_Term The classic navigation object to convert.
1906 + * @param object $classic_nav_menu WP_Term The classic navigation object to convert.
1709 1907 * @return array the normalized parsed blocks.
1710 1908 */
1711 1909 function gutenberg_block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
1712 1910