PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
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.3.0 → 24.1.0 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