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 +234 -26 23.0.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 ) ) {
@@ -424,9 +469,13 @@
424 469 // Construct the full template part ID for get_block_file_template.
425 470 $full_template_part_id = $theme . '//' . $slug;
426 471 $block_template = get_block_file_template( $full_template_part_id, 'wp_template_part' );
427 472 if ( isset( $block_template->content ) ) {
428 - $parsed_blocks = parse_blocks( $block_template->content );
473 + // Expand shortcodes before parsing blocks, matching the order in
474 + // `render_block_core_template_part()`.
475 + $content = shortcode_unautop( $block_template->content );
476 + $content = do_shortcode( $content );
477 + $parsed_blocks = parse_blocks( $content );
429 478 $blocks = gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
430 479 // Disable overlay menu for any navigation blocks within the overlay to prevent nested overlays.
431 480 $blocks = static::disable_overlay_menu_for_nested_navigation_blocks( $blocks );
432 481 return new WP_Block_List( $blocks, $attributes );
@@ -448,8 +497,14 @@
448 497
449 498 // Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
450 499 $markup = serialize_blocks( $blocks );
451 500 $markup = apply_block_hooks_to_content_from_post_object( $markup, $template_part_post );
501 +
502 + // Expand shortcodes before parsing blocks, matching the order in
503 + // `render_block_core_template_part()`.
504 + $markup = shortcode_unautop( $markup );
505 + $markup = do_shortcode( $markup );
506 +
452 507 $blocks = parse_blocks( $markup );
453 508
454 509 // Disable overlay menu for any navigation blocks within the overlay to prevent nested overlays.
455 510 $blocks = static::disable_overlay_menu_for_nested_navigation_blocks( $blocks );
@@ -462,9 +517,9 @@
462 517 *
463 518 * @since 6.5.0
464 519 *
465 520 * @param array $attributes The block attributes.
466 - * @param WP_Block $block The parsed block.
521 + * @param WP_Block $block The parsed block.
467 522 * @return WP_Block_List Returns the inner blocks for the navigation block.
468 523 */
469 524 private static function get_inner_blocks( $attributes, $block ) {
470 525 $inner_blocks = $block->inner_blocks;
@@ -565,14 +620,15 @@
565 620 'center' => 'items-justified-center',
566 621 'space-between' => 'items-justified-space-between',
567 622 );
568 623
569 - $layout_class = '';
624 + $layout_class = '';
625 + $nav_justify_content = $attributes['layout']['justifyContent'] ?? null;
570 626 if (
571 - isset( $attributes['layout']['justifyContent'] ) &&
572 - isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
627 + is_string( $nav_justify_content ) &&
628 + isset( $layout_justification[ $nav_justify_content ] )
573 629 ) {
574 - $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
630 + $layout_class .= $layout_justification[ $nav_justify_content ];
575 631 }
576 632 if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
577 633 $layout_class .= ' is-vertical';
578 634 }
@@ -632,11 +688,16 @@
632 688 *
633 689 * @since 7.0.0
634 690 *
635 691 * @param bool $is_hidden_by_default Whether the responsive menu is hidden by default.
636 - * @param bool $has_custom_overlay Whether a custom overlay is used.
637 - * @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.
638 694 * @return array Returns the responsive container classes.
695 + *
696 + * @phpstan-param array{
697 + * overlay_css_classes: list<string>,
698 + * ...
699 + * } $colors
639 700 */
640 701 private static function get_responsive_container_classes( $is_hidden_by_default, $has_custom_overlay, $colors ) {
641 702 $responsive_container_classes = array( 'wp-block-navigation__responsive-container' );
642 703
@@ -659,10 +720,16 @@
659 720 * Get overlay inline styles for the navigation block.
660 721 *
661 722 * @since 7.0.0
662 723 *
663 - * @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.
664 726 * @return string Returns the overlay inline styles.
727 + *
728 + * @phpstan-param array{
729 + * overlay_inline_styles: string,
730 + * ...
731 + * } $colors
665 732 */
666 733 private static function get_overlay_inline_styles( $has_custom_overlay, $colors ) {
667 734 $overlay_inline_styles = $has_custom_overlay ? '' : esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) );
668 735 return ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '';
@@ -672,10 +739,10 @@
672 739 * Get the responsive container markup
673 740 *
674 741 * @since 6.5.0
675 742 *
676 - * @param array $attributes The block attributes.
677 - * @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.
678 745 * @param string $inner_blocks_html The markup for the inner blocks.
679 746 * @return string Returns the container markup.
680 747 */
681 748 private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) {
@@ -764,8 +831,16 @@
764 831 ';
765 832 $responsive_container_content_directives = '
766 833 data-wp-watch="callbacks.focusFirstElement"
767 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 + }
768 843 }
769 844
770 845 // Don't apply overlay inline styles if using a custom overlay template part.
771 846 // The custom overlay is responsible for its own styling.
@@ -824,10 +899,10 @@
824 899 * Get the wrapper attributes
825 900 *
826 901 * @since 6.5.0
827 902 *
828 - * @param array $attributes The block attributes.
829 - * @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.
830 905 * @return string Returns the navigation block markup.
831 906 */
832 907 private static function get_nav_attributes( $attributes, $inner_blocks ) {
833 908 $is_interactive = static::is_interactive( $attributes, $inner_blocks );
@@ -911,9 +986,9 @@
911 986 * Returns the markup for the navigation block.
912 987 *
913 988 * @since 6.5.0
914 989 *
915 - * @param array $attributes The block attributes.
990 + * @param array $attributes The block attributes.
916 991 * @param WP_Block_List $inner_blocks The list of inner blocks.
917 992 * @return string Returns the navigation wrapper markup.
918 993 */
919 994 private static function get_inner_block_markup( $attributes, $inner_blocks ) {
@@ -980,8 +1055,12 @@
980 1055 }
981 1056
982 1057 unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
983 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 +
984 1063 $inner_blocks = static::get_inner_blocks( $attributes, $block );
985 1064 // Prevent navigation blocks referencing themselves from rendering.
986 1065 if ( gutenberg_block_core_navigation_block_tree_has_block_type(
987 1066 $inner_blocks,
@@ -1200,9 +1279,9 @@
1200 1279 'class_name' => 'wp-block-navigation-submenu__toggle',
1201 1280 )
1202 1281 ) ) {
1203 1282 $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' );
1204 - $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' );
1283 + $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isSubmenuOpen' );
1205 1284 // The `aria-expanded` attribute for SSR is already added in the submenu block.
1206 1285 }
1207 1286 // Add directives to the submenu.
1208 1287 if ( $tags->next_tag(
@@ -1547,8 +1626,137 @@
1547 1626
1548 1627 add_action( 'init', 'gutenberg_register_block_core_navigation', 20 );
1549 1628
1550 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 +/**
1551 1759 * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
1552 1760 *
1553 1761 * @since 5.9.0
1554 1762 *
@@ -1589,14 +1797,14 @@
1589 1797 * @since 5.9.0
1590 1798 *
1591 1799 * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead.
1592 1800 *
1593 - * @param array $menu_items An array of menu items that represent
1594 - * an individual level of a menu.
1595 - * @param array $menu_items_by_parent_id An array keyed by the id of the
1596 - * parent menu where each element is an
1597 - * array of menu items that belong to
1598 - * 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.
1599 1807 * @return array An array of parsed block data.
1600 1808 */
1601 1809 function gutenberg_block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
1602 1810
@@ -1694,9 +1902,9 @@
1694 1902 * @since 6.2.0
1695 1903 *
1696 1904 * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead.
1697 1905 *
1698 - * @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.
1699 1907 * @return array the normalized parsed blocks.
1700 1908 */
1701 1909 function gutenberg_block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
1702 1910