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 +171 -31 23.5.0 → 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,9 +1626,9 @@
1557 1626
1558 1627 add_action( 'init', 'gutenberg_register_block_core_navigation', 20 );
1559 1628
1560 1629 /**
1561 - * Adds the Navigation block state class to inner list containers.
1630 + * Adds Navigation block support classes to inner list containers.
1562 1631 *
1563 1632 * State block support adds the generated `wp-states-*` class to the outer
1564 1633 * block wrapper. The Navigation block renders its menu items inside an inner
1565 1634 * `wp-block-navigation__container` list, so the same state class is also needed
@@ -1564,8 +1633,13 @@
1564 1633 * block wrapper. The Navigation block renders its menu items inside an inner
1565 1634 * `wp-block-navigation__container` list, so the same state class is also needed
1566 1635 * there for state styles to apply directly to the menu list.
1567 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 + *
1568 1642 * Currently this is required as a workaround because of how difficult it is for nav
1569 1643 * child blocks to inherit styles through the complex responsive nav block html. The
1570 1644 * bug in https://github.com/WordPress/gutenberg/issues/62690 also prevents inheritance.
1571 1645 *
@@ -1574,13 +1648,49 @@
1574 1648 * @param string $block_content The block content.
1575 1649 * @param array $block The full block, including name and attributes.
1576 1650 * @return string The updated block content.
1577 1651 */
1578 -function gutenberg_block_core_navigation_add_state_class_to_container( $block_content, $block ) {
1652 +function gutenberg_block_core_navigation_add_support_classes_to_container( $block_content, $block ) {
1579 1653 if ( 'core/navigation' !== ( $block['blockName'] ?? null ) || empty( $block_content ) ) {
1580 1654 return $block_content;
1581 1655 }
1582 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 +
1583 1693 $processor = new WP_HTML_Tag_Processor( $block_content );
1584 1694 if ( ! $processor->next_tag() ) {
1585 1695 return $block_content;
1586 1696 }
@@ -1585,16 +1695,38 @@
1585 1695 return $block_content;
1586 1696 }
1587 1697
1588 1698 $class_attribute = $processor->get_attribute( 'class' );
1589 - if ( ! is_string( $class_attribute ) || ! preg_match( '/\bwp-states-[a-f0-9]{8}\b/', $class_attribute, $matches ) ) {
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 ) {
1590 1723 return $block_content;
1591 1724 }
1592 1725
1593 - $state_class = $matches[0];
1594 1726 while ( $processor->next_tag() ) {
1595 1727 // Custom overlay content can include nested Navigation blocks.
1596 - // Avoid applying the outer Navigation state class to an inner nav block.
1728 + // Avoid applying the outer Navigation classes to an inner nav block.
1597 1729 if ( $processor->has_class( 'wp-block-navigation' ) && ! $processor->has_class( 'wp-block-navigation__container' ) ) {
1598 1730 break;
1599 1731 }
1600 1732
@@ -1601,8 +1733,16 @@
1601 1733 if ( ! $processor->has_class( 'wp-block-navigation__container' ) ) {
1602 1734 continue;
1603 1735 }
1604 1736
1737 + if ( null !== $layout_class ) {
1738 + $processor->add_class( $layout_class );
1739 + }
1740 +
1741 + if ( null === $state_class ) {
1742 + continue;
1743 + }
1744 +
1605 1745 $class_attribute = $processor->get_attribute( 'class' );
1606 1746 if ( is_string( $class_attribute ) && preg_match( '/\bwp-states-[a-f0-9]{8}\b/', $class_attribute ) ) {
1607 1747 continue;
1608 1748 }
@@ -1612,9 +1752,9 @@
1612 1752
1613 1753 return $processor->get_updated_html();
1614 1754 }
1615 1755
1616 -add_filter( 'render_block', 'gutenberg_block_core_navigation_add_state_class_to_container', 11, 2 );
1756 +add_filter( 'render_block', 'gutenberg_block_core_navigation_add_support_classes_to_container', 11, 2 );
1617 1757
1618 1758 /**
1619 1759 * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
1620 1760 *
@@ -1657,14 +1797,14 @@
1657 1797 * @since 5.9.0
1658 1798 *
1659 1799 * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead.
1660 1800 *
1661 - * @param array $menu_items An array of menu items that represent
1662 - * an individual level of a menu.
1663 - * @param array $menu_items_by_parent_id An array keyed by the id of the
1664 - * parent menu where each element is an
1665 - * array of menu items that belong to
1666 - * 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.
1667 1807 * @return array An array of parsed block data.
1668 1808 */
1669 1809 function gutenberg_block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
1670 1810
@@ -1762,9 +1902,9 @@
1762 1902 * @since 6.2.0
1763 1903 *
1764 1904 * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead.
1765 1905 *
1766 - * @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.
1767 1907 * @return array the normalized parsed blocks.
1768 1908 */
1769 1909 function gutenberg_block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
1770 1910