diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml index ff1f1eb980347b..2a7ea040f8b493 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['20', '21'] + node: ['20', '22'] os: ['macos-latest', 'ubuntu-latest', 'windows-latest'] steps: diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 570fd8477b5fed..a813e4d2d8f5ba 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['20', '21'] + node: ['20', '22'] shard: ['1/4', '2/4', '3/4', '4/4'] steps: @@ -66,7 +66,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['20', '21'] + node: ['20', '22'] steps: - name: Checkout repository diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php index 5c5688e7cdf43e..b91e89103faa02 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -46,176 +46,179 @@ function gutenberg_register_metadata_attribute( $args ) { } add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' ); -/** - * Depending on the block attribute name, replace its value in the HTML based on the value provided. - * - * @param string $block_content Block Content. - * @param string $block_name The name of the block to process. - * @param string $attribute_name The attribute name to replace. - * @param mixed $source_value The value used to replace in the HTML. - * @return string The modified block content. - */ -function gutenberg_block_bindings_replace_html( $block_content, $block_name, string $attribute_name, $source_value ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); - if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { - return $block_content; - } +// Only process block bindings if they are not processed in core. +if ( ! class_exists( 'WP_Block_Bindings_Registry' ) ) { + /** + * Depending on the block attribute name, replace its value in the HTML based on the value provided. + * + * @param string $block_content Block Content. + * @param string $block_name The name of the block to process. + * @param string $attribute_name The attribute name to replace. + * @param mixed $source_value The value used to replace in the HTML. + * @return string The modified block content. + */ + function gutenberg_block_bindings_replace_html( $block_content, $block_name, string $attribute_name, $source_value ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { + return $block_content; + } - // Depending on the attribute source, the processing will be different. - switch ( $block_type->attributes[ $attribute_name ]['source'] ) { - case 'html': - case 'rich-text': - $block_reader = new WP_HTML_Tag_Processor( $block_content ); - - // TODO: Support for CSS selectors whenever they are ready in the HTML API. - // In the meantime, support comma-separated selectors by exploding them into an array. - $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] ); - // Add a bookmark to the first tag to be able to iterate over the selectors. - $block_reader->next_tag(); - $block_reader->set_bookmark( 'iterate-selectors' ); - - // TODO: This shouldn't be needed when the `set_inner_html` function is ready. - // Store the parent tag and its attributes to be able to restore them later in the button. - // The button block has a wrapper while the paragraph and heading blocks don't. - if ( 'core/button' === $block_name ) { - $button_wrapper = $block_reader->get_tag(); - $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); - $button_wrapper_attrs = array(); - foreach ( $button_wrapper_attribute_names as $name ) { - $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); + // Depending on the attribute source, the processing will be different. + switch ( $block_type->attributes[ $attribute_name ]['source'] ) { + case 'html': + case 'rich-text': + $block_reader = new WP_HTML_Tag_Processor( $block_content ); + + // TODO: Support for CSS selectors whenever they are ready in the HTML API. + // In the meantime, support comma-separated selectors by exploding them into an array. + $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] ); + // Add a bookmark to the first tag to be able to iterate over the selectors. + $block_reader->next_tag(); + $block_reader->set_bookmark( 'iterate-selectors' ); + + // TODO: This shouldn't be needed when the `set_inner_html` function is ready. + // Store the parent tag and its attributes to be able to restore them later in the button. + // The button block has a wrapper while the paragraph and heading blocks don't. + if ( 'core/button' === $block_name ) { + $button_wrapper = $block_reader->get_tag(); + $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); + $button_wrapper_attrs = array(); + foreach ( $button_wrapper_attribute_names as $name ) { + $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); + } } - } - foreach ( $selectors as $selector ) { - // If the parent tag, or any of its children, matches the selector, replace the HTML. - if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( - array( - 'tag_name' => $selector, - ) - ) ) { - $block_reader->release_bookmark( 'iterate-selectors' ); - - // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. - // Until then, it is hardcoded for the paragraph, heading, and button blocks. - // Store the tag and its attributes to be able to restore them later. - $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); - $selector_attrs = array(); - foreach ( $selector_attribute_names as $name ) { - $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); - } - $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . ""; - $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); - $amended_content->next_tag(); - foreach ( $selector_attrs as $attribute_key => $attribute_value ) { - $amended_content->set_attribute( $attribute_key, $attribute_value ); - } - if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) { - return $amended_content->get_updated_html(); - } - if ( 'core/button' === $block_name ) { - $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}"; - $amended_button = new WP_HTML_Tag_Processor( $button_markup ); - $amended_button->next_tag(); - foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { - $amended_button->set_attribute( $attribute_key, $attribute_value ); + foreach ( $selectors as $selector ) { + // If the parent tag, or any of its children, matches the selector, replace the HTML. + if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( + array( + 'tag_name' => $selector, + ) + ) ) { + $block_reader->release_bookmark( 'iterate-selectors' ); + + // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. + // Until then, it is hardcoded for the paragraph, heading, and button blocks. + // Store the tag and its attributes to be able to restore them later. + $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); + $selector_attrs = array(); + foreach ( $selector_attribute_names as $name ) { + $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); + } + $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . ""; + $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); + $amended_content->next_tag(); + foreach ( $selector_attrs as $attribute_key => $attribute_value ) { + $amended_content->set_attribute( $attribute_key, $attribute_value ); + } + if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) { + return $amended_content->get_updated_html(); + } + if ( 'core/button' === $block_name ) { + $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}"; + $amended_button = new WP_HTML_Tag_Processor( $button_markup ); + $amended_button->next_tag(); + foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { + $amended_button->set_attribute( $attribute_key, $attribute_value ); + } + return $amended_button->get_updated_html(); } - return $amended_button->get_updated_html(); + } else { + $block_reader->seek( 'iterate-selectors' ); } - } else { - $block_reader->seek( 'iterate-selectors' ); } - } - $block_reader->release_bookmark( 'iterate-selectors' ); - return $block_content; - - case 'attribute': - $amended_content = new WP_HTML_Tag_Processor( $block_content ); - if ( ! $amended_content->next_tag( - array( - // TODO: build the query from CSS selector. - 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'], - ) - ) ) { + $block_reader->release_bookmark( 'iterate-selectors' ); return $block_content; - } - $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value ); - return $amended_content->get_updated_html(); - default: - return $block_content; - } -} + case 'attribute': + $amended_content = new WP_HTML_Tag_Processor( $block_content ); + if ( ! $amended_content->next_tag( + array( + // TODO: build the query from CSS selector. + 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'], + ) + ) ) { + return $block_content; + } + $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value ); + return $amended_content->get_updated_html(); -/** - * Process the block bindings attribute. - * - * @param string $block_content Block Content. - * @param array $parsed_block The full block, including name and attributes. - * @param WP_Block $block_instance The block instance. - * @return string Block content with the bind applied. - */ -function gutenberg_process_block_bindings( $block_content, $parsed_block, $block_instance ) { - $supported_block_attrs = array( - 'core/paragraph' => array( 'content' ), - 'core/heading' => array( 'content' ), - 'core/image' => array( 'id', 'url', 'title', 'alt' ), - 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), - ); - - // If the block doesn't have the bindings property or isn't one of the supported block types, return. - if ( - ! isset( $supported_block_attrs[ $block_instance->name ] ) || - empty( $parsed_block['attrs']['metadata']['bindings'] ) || - ! is_array( $parsed_block['attrs']['metadata']['bindings'] ) - ) { - return $block_content; + default: + return $block_content; + } } - /* - * Assuming the following format for the bindings property of the "metadata" attribute: + /** + * Process the block bindings attribute. * - * "bindings": { - * "title": { - * "source": "core/post-meta", - * "args": { "key": "text_custom_field" } - * }, - * "url": { - * "source": "core/post-meta", - * "args": { "key": "url_custom_field" } - * } - * } + * @param string $block_content Block Content. + * @param array $parsed_block The full block, including name and attributes. + * @param WP_Block $block_instance The block instance. + * @return string Block content with the bind applied. */ + function gutenberg_process_block_bindings( $block_content, $parsed_block, $block_instance ) { + $supported_block_attrs = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'id', 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), + ); - $modified_block_content = $block_content; - foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) { - // If the attribute is not in the supported list, process next attribute. - if ( ! in_array( $attribute_name, $supported_block_attrs[ $block_instance->name ], true ) ) { - continue; - } - // If no source is provided, or that source is not registered, process next attribute. - if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) { - continue; + // If the block doesn't have the bindings property or isn't one of the supported block types, return. + if ( + ! isset( $supported_block_attrs[ $block_instance->name ] ) || + empty( $parsed_block['attrs']['metadata']['bindings'] ) || + ! is_array( $parsed_block['attrs']['metadata']['bindings'] ) + ) { + return $block_content; } - $block_binding_source = get_block_bindings_source( $block_binding['source'] ); - if ( null === $block_binding_source ) { - continue; - } + /* + * Assuming the following format for the bindings property of the "metadata" attribute: + * + * "bindings": { + * "title": { + * "source": "core/post-meta", + * "args": { "key": "text_custom_field" } + * }, + * "url": { + * "source": "core/post-meta", + * "args": { "key": "url_custom_field" } + * } + * } + */ + + $modified_block_content = $block_content; + foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) { + // If the attribute is not in the supported list, process next attribute. + if ( ! in_array( $attribute_name, $supported_block_attrs[ $block_instance->name ], true ) ) { + continue; + } + // If no source is provided, or that source is not registered, process next attribute. + if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) { + continue; + } + + $block_binding_source = get_block_bindings_source( $block_binding['source'] ); + if ( null === $block_binding_source ) { + continue; + } - $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array(); - $source_value = $block_binding_source->get_value( $source_args, $block_instance, $attribute_name ); + $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array(); + $source_value = $block_binding_source->get_value( $source_args, $block_instance, $attribute_name ); - // If the value is not null, process the HTML based on the block and the attribute. - if ( ! is_null( $source_value ) ) { - $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $attribute_name, $source_value ); + // If the value is not null, process the HTML based on the block and the attribute. + if ( ! is_null( $source_value ) ) { + $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $attribute_name, $source_value ); + } } + + return $modified_block_content; } - return $modified_block_content; + add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 ); } -add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 ); - /** * Enable the viewStyle block API for core versions < 6.5 * diff --git a/lib/experimental/posts/load.php b/lib/experimental/posts/load.php new file mode 100644 index 00000000000000..56a600ab97c05d --- /dev/null +++ b/lib/experimental/posts/load.php @@ -0,0 +1,43 @@ +'; +} + +/** + * Replaces the default posts menu item with the new posts dashboard. + */ +function gutenberg_replace_posts_dashboard() { + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + if ( ! $gutenberg_experiments || ! array_key_exists( 'gutenberg-new-posts-dashboard', $gutenberg_experiments ) || ! $gutenberg_experiments['gutenberg-new-posts-dashboard'] ) { + return; + } + $ptype_obj = get_post_type_object( 'post' ); + add_submenu_page( + 'gutenberg', + $ptype_obj->labels->name, + $ptype_obj->labels->name, + 'edit_posts', + 'gutenberg-posts-dashboard', + 'gutenberg_posts_dashboard' + ); +} diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 5147edda0deec5..ca5c87eec737be 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -139,6 +139,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-new-posts-dashboard', + __( 'Redesigned posts dashboard', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable a redesigned posts dashboard.', 'gutenberg' ), + 'id' => 'gutenberg-new-posts-dashboard', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/lib/load.php b/lib/load.php index 45b8f1a1ceb3b0..201bfe1967d5b9 100644 --- a/lib/load.php +++ b/lib/load.php @@ -149,6 +149,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/l10n.php'; require __DIR__ . '/experimental/synchronization.php'; require __DIR__ . '/experimental/script-modules.php'; +require __DIR__ . '/experimental/posts/load.php'; if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { require __DIR__ . '/experimental/disable-tinymce.php'; diff --git a/package-lock.json b/package-lock.json index e2a875e89a92a8..7e20ec0fa393dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55127,7 +55127,7 @@ }, "packages/react-native-aztec": { "name": "@wordpress/react-native-aztec", - "version": "1.119.1", + "version": "1.120.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/element": "file:../element", @@ -55144,7 +55144,7 @@ }, "packages/react-native-bridge": { "name": "@wordpress/react-native-bridge", - "version": "1.119.1", + "version": "1.120.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/react-native-aztec": "file:../react-native-aztec" @@ -55159,7 +55159,7 @@ }, "packages/react-native-editor": { "name": "@wordpress/react-native-editor", - "version": "1.119.1", + "version": "1.120.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 0e3dddf70632b3..c8c35acfce4867 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -189,8 +189,6 @@ $z-layers: ( ".customize-widgets__block-toolbar": 7, // Site editor layout - ".edit-site-layout__header-container": 4, - ".edit-site-layout__hub": 3, ".edit-site-page-header": 2, ".edit-site-page-content": 1, ".edit-site-patterns__dataviews-list-pagination": 2, diff --git a/packages/block-editor/src/components/inserter/block-types-tab.js b/packages/block-editor/src/components/inserter/block-types-tab.js index 42c4a6a35e14bf..50a8b46b46427c 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.js @@ -207,7 +207,6 @@ export function BlockTypesTab( showMostUsedBlocks={ showMostUsedBlocks } className="block-editor-inserter__insertable-blocks-at-selection" /> -
) } set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' ); $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' ); - $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' ); + $tags->set_attribute( 'data-wp-on-async--focusout', 'actions.handleMenuFocusout' ); $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' ); // This is a fix for Safari. Without it, Safari doesn't change the active @@ -836,8 +836,8 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut $tags->set_attribute( 'tabindex', '-1' ); if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { - $tags->set_attribute( 'data-wp-on--mouseenter', 'actions.openMenuOnHover' ); - $tags->set_attribute( 'data-wp-on--mouseleave', 'actions.closeMenuOnHover' ); + $tags->set_attribute( 'data-wp-on-async--mouseenter', 'actions.openMenuOnHover' ); + $tags->set_attribute( 'data-wp-on-async--mouseleave', 'actions.closeMenuOnHover' ); } // Add directives to the toggle submenu button. @@ -847,7 +847,7 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut 'class_name' => 'wp-block-navigation-submenu__toggle', ) ) ) { - $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' ); + $tags->set_attribute( 'data-wp-on-async--click', 'actions.toggleMenuOnClick' ); $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' ); // The `aria-expanded` attribute for SSR is already added in the submenu block. } @@ -858,7 +858,7 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut 'class_name' => 'wp-block-navigation__submenu-container', ) ) ) { - $tags->set_attribute( 'data-wp-on--focus', 'actions.openMenuOnFocus' ); + $tags->set_attribute( 'data-wp-on-async--focus', 'actions.openMenuOnFocus' ); } // Iterate through subitems if exist. diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index 14d09e061848e7..9da7ab70d84f33 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -143,7 +143,7 @@ const { state, actions } = store( // If focus is outside modal, and in the document, close menu // event.target === The element losing focus // event.relatedTarget === The element receiving focus (if any) - // When focusout is outsite the document, + // When focusout is outside the document, // `window.document.activeElement` doesn't change. // The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari. diff --git a/packages/block-library/src/query-pagination-next/index.php b/packages/block-library/src/query-pagination-next/index.php index d574f940938469..3b5be47fbed371 100644 --- a/packages/block-library/src/query-pagination-next/index.php +++ b/packages/block-library/src/query-pagination-next/index.php @@ -77,7 +77,7 @@ function render_block_core_query_pagination_next( $attributes, $content, $block ) ) { $p->set_attribute( 'data-wp-key', 'query-pagination-next' ); $p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' ); - $p->set_attribute( 'data-wp-on--mouseenter', 'core/query::actions.prefetch' ); + $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' ); $p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' ); $content = $p->get_updated_html(); } diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php index 95ed54075a7c54..1592f0a10cbff5 100644 --- a/packages/block-library/src/query-pagination-previous/index.php +++ b/packages/block-library/src/query-pagination-previous/index.php @@ -63,7 +63,7 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl ) ) { $p->set_attribute( 'data-wp-key', 'query-pagination-previous' ); $p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' ); - $p->set_attribute( 'data-wp-on--mouseenter', 'core/query::actions.prefetch' ); + $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' ); $p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' ); $content = $p->get_updated_html(); } diff --git a/packages/block-library/src/query/editor.scss b/packages/block-library/src/query/editor.scss index 61b553b6ed02e0..9b9db430a81a65 100644 --- a/packages/block-library/src/query/editor.scss +++ b/packages/block-library/src/query/editor.scss @@ -39,7 +39,8 @@ position: sticky; top: 0; padding: $grid-unit-20 0; - margin-bottom: 2px; + transform: translateY(- $grid-unit-05); // Offsets the top padding on the modal content container + margin-bottom: - $grid-unit-05; z-index: z-index(".block-library-query-pattern__selection-search"); } } diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index 9d4c61f4163127..39b8591c86600f 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -191,8 +191,8 @@ function render_block_core_search( $attributes ) { data-wp-interactive="core/search"' . $form_context . 'data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" - data-wp-on--keydown="actions.handleSearchKeydown" - data-wp-on--focusout="actions.handleSearchFocusout" + data-wp-on-async--keydown="actions.handleSearchKeydown" + data-wp-on-async--focusout="actions.handleSearchFocusout" '; } diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 1f9bb64c7889d7..3a0c8869175c47 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -110,7 +110,8 @@ export function items( state = {}, action ) { [ context ]: { ...state[ context ], ...action.items.reduce( ( accumulator, value ) => { - const itemId = value[ key ]; + const itemId = value?.[ key ]; + accumulator[ itemId ] = conservativeMapItem( state?.[ context ]?.[ itemId ], value @@ -164,7 +165,7 @@ export function itemIsComplete( state = {}, action ) { [ context ]: { ...state[ context ], ...action.items.reduce( ( result, item ) => { - const itemId = item[ key ]; + const itemId = item?.[ key ]; // Defer to completeness if already assigned. Technically the // data may be outdated if receiving items for a field subset. @@ -232,7 +233,7 @@ const receiveQueries = compose( [ return { itemIds: getMergedItemIds( state?.itemIds || [], - action.items.map( ( item ) => item[ key ] ), + action.items.map( ( item ) => item?.[ key ] ).filter( Boolean ), page, perPage ), diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 8e6be425244687..3d6473d7610366 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -256,7 +256,7 @@ function entity( entityConfig ) { const nextState = { ...state }; for ( const record of action.items ) { - const recordId = record[ action.key ]; + const recordId = record?.[ action.key ]; const edits = nextState[ recordId ]; if ( ! edits ) { continue; diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 3e5373eda6d6ab..167b8ac052fe45 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -278,7 +278,7 @@ export const getEntityRecords = if ( ! query?._fields && ! query.context ) { const key = entityConfig.key || DEFAULT_ENTITY_KEY; const resolutionsArgs = records - .filter( ( record ) => record[ key ] ) + .filter( ( record ) => record?.[ key ] ) .map( ( record ) => [ kind, name, record[ key ] ] ); dispatch( { diff --git a/packages/edit-post/src/components/init-pattern-modal/index.js b/packages/edit-post/src/components/init-pattern-modal/index.js index c9bca96be05f44..1f181006cfec82 100644 --- a/packages/edit-post/src/components/init-pattern-modal/index.js +++ b/packages/edit-post/src/components/init-pattern-modal/index.js @@ -84,10 +84,7 @@ export default function InitPatternModal() { /> { isEditMode && } - { showVisualEditor && } { ! isReady ? : null } { isEditMode && } { isReady && ( @@ -213,6 +209,23 @@ export default function EditSiteEditor( { isLoading } ) { ! isEditingPage && } > + { isEditMode && ( + + { ( { length } ) => + length <= 1 && ( + + ) + } + + ) } { supportsGlobalStyles && } diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index f44b5a6f02ce20..b157057062c9d1 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -17,7 +17,3 @@ display: flex; justify-content: center; } - -.editor-header { - padding-left: $header-height; -} diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 4bbf29cced39ea..58de32d4b46878 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -16,9 +16,10 @@ import { useReducedMotion, useViewportMatch, useResizeObserver, + usePrevious, } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; +import { useState, useRef, useEffect } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { CommandMenu, @@ -72,7 +73,7 @@ export default function Layout() { useCommonCommands(); const isMobileViewport = useViewportMatch( 'medium', '<' ); - + const toggleRef = useRef(); const { isDistractionFree, hasFixedToolbar, @@ -120,27 +121,6 @@ export default function Layout() { triggerAnimationOnChange: canvasMode + '__' + routeKey, } ); - // This determines which animation variant should apply to the header. - // There is also a `isDistractionFreeHovering` state that gets priority - // when hovering the `edit-site-layout__header-container` in distraction - // free mode. It's set via framer and trickles down to all the children - // so they can use this variant state too. - // - // TODO: The issue with this is we want to have the hover state stick when hovering - // a popover opened via the header. We'll probably need to lift this state to - // handle it ourselves. Also, focusWithin the header needs to be handled. - let headerAnimationState; - - if ( canvasMode === 'view' ) { - // We need 'view' to always take priority so 'isDistractionFree' - // doesn't bleed over into the view (sidebar) state - headerAnimationState = 'view'; - } else if ( isDistractionFree ) { - headerAnimationState = 'isDistractionFree'; - } else { - headerAnimationState = canvasMode; // edit, view, init - } - // Sets the right context for the command palette let commandContext = 'site-editor'; @@ -154,6 +134,14 @@ export default function Layout() { const [ backgroundColor ] = useGlobalStyle( 'color.background' ); const [ gradientValue ] = useGlobalStyle( 'color.gradient' ); + const previousCanvaMode = usePrevious( canvasMode ); + useEffect( () => { + if ( previousCanvaMode === 'edit' ) { + toggleRef.current?.focus(); + } + // Should not depend on the previous canvas mode value but the next. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ canvasMode ] ); // Synchronizing the URL with the store value of canvasMode happens in an effect // This condition ensures the component is only rendered after the synchronization happens @@ -183,41 +171,6 @@ export default function Layout() { } ) } > - - - -
{ /* The NavigableRegion must always be rendered and not use @@ -246,6 +199,12 @@ export default function Layout() { } } className="edit-site-layout__sidebar" > + { areas.sidebar } diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 0c5412b6d765bb..01c31de0d65d6c 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -11,37 +11,6 @@ } } -.edit-site-layout__hub { - position: fixed; - top: 0; - left: 0; - width: calc(100vw - #{$canvas-padding * 2}); - height: $header-height; - z-index: z-index(".edit-site-layout__hub"); - - @include break-medium { - width: calc(#{$nav-sidebar-width} - #{$grid-unit-15}); - } - - .edit-site-layout.is-full-canvas & { - padding-right: 0; - border-radius: 0; - width: $header-height; - box-shadow: none; - } -} - -.edit-site-layout__header-container:has(+ .edit-site-layout__content > .edit-site-layout__mobile>.edit-site-page) { - margin-bottom: $header-height; - @include break-medium { - margin-bottom: 0; - } -} - -.edit-site-layout__header-container { - z-index: z-index(".edit-site-layout__header-container"); -} - .edit-site-layout__content { height: 100%; flex-grow: 1; @@ -163,10 +132,22 @@ height: 100%; } +/* stylelint-disable -- Disable reason: View Transitions not supported properly by stylelint. */ +html.canvas-mode-edit-transition::view-transition-group(toggle) { + animation-delay: 255ms; +} +/* stylelint-enable */ + +.edit-site-layout.is-full-canvas .edit-site-layout__sidebar-region .edit-site-layout__view-mode-toggle { + display: none; +} + .edit-site-layout__view-mode-toggle.components-button { + /* stylelint-disable -- Disable reason: View Transitions not supported properly by stylelint. */ + view-transition-name: toggle; + /* stylelint-enable */ position: relative; color: $white; - border-radius: 0; height: $header-height; width: $header-height; overflow: hidden; @@ -174,6 +155,8 @@ display: flex; align-items: center; justify-content: center; + background: $gray-900; + border-radius: 0; &:hover, &:active { @@ -207,7 +190,6 @@ .edit-site-layout__view-mode-toggle-icon { display: flex; - border-radius: $radius-block-ui; height: $grid-unit-80; width: $grid-unit-80; justify-content: center; @@ -244,33 +226,6 @@ } } -.edit-site-layout.is-distraction-free { - .edit-site-layout__header-container { - height: $header-height; - position: absolute; - top: 0; - left: 0; - right: 0; - z-index: z-index(".edit-site-layout__header-container"); - width: 100%; - - // We need ! important because we override inline styles - // set by the motion component. - &:focus-within { - opacity: 1 !important; - div { - transform: translateX(0) translateY(0) translateZ(0) !important; - } - } - } - - .edit-site-site-hub { - position: absolute; - top: 0; - z-index: z-index(".edit-site-layout__hub"); - } -} - .edit-site-layout__area { flex-grow: 1; margin: 0; diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 223ede9ac1d988..47727bab047d46 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -86,15 +86,12 @@ const DEFAULT_VIEW = { const SYNC_FILTERS = [ { value: PATTERN_SYNC_TYPES.full, - label: _x( 'Synced', 'Option that shows all synchronized patterns' ), + label: _x( 'Synced', 'pattern (singular)' ), description: __( 'Patterns that are kept in sync across the site.' ), }, { value: PATTERN_SYNC_TYPES.unsynced, - label: _x( - 'Not synced', - 'Option that shows all patterns that are not synchronized' - ), + label: _x( 'Not synced', 'pattern (singular)' ), description: __( 'Patterns that can be changed freely without affecting the site.' ), @@ -298,13 +295,19 @@ export default function DataviewsPatterns() { - { SYNC_FILTERS.find( - ( { value } ) => value === item.syncStatus - )?.label || - SYNC_FILTERS.find( - ( { value } ) => - value === PATTERN_SYNC_TYPES.unsynced - ).label } + { + ( + SYNC_FILTERS.find( + ( { value } ) => + value === item.syncStatus + ) || + SYNC_FILTERS.find( + ( { value } ) => + value === + PATTERN_SYNC_TYPES.unsynced + ) + ).label + } ); }, diff --git a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js index e6ebd5b1f8ee8b..0b5f93546875e5 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js +++ b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js @@ -35,11 +35,15 @@ export default function DataViewItem( { const iconToUse = icon || VIEW_LAYOUTS.find( ( v ) => v.type === type ).icon; + let activeView = isCustom ? customViewId : slug; + if ( activeView === 'all' ) { + activeView = undefined; + } const linkInfo = useLink( { postType, layout, - activeView: isCustom ? customViewId : slug, - isCustom: isCustom ? 'true' : 'false', + activeView, + isCustom: isCustom ? 'true' : undefined, } ); return ( { - const { canvasMode, dashboardLink, homeUrl, siteTitle } = useSelect( - ( select ) => { - const { getCanvasMode, getSettings } = unlock( - select( editSiteStore ) - ); +const SiteHub = memo( + forwardRef( ( { isTransparent }, ref ) => { + const { dashboardLink, homeUrl, siteTitle } = useSelect( ( select ) => { + const { getSettings } = unlock( select( editSiteStore ) ); const { getSite, @@ -47,7 +35,6 @@ const SiteHub = memo( ( { isTransparent, className } ) => { } = select( coreStore ); const _site = getSite(); return { - canvasMode: getCanvasMode(), dashboardLink: getSettings().__experimentalDashboardLink || 'index.php', homeUrl: getUnstableBase()?.home, @@ -56,141 +43,63 @@ const SiteHub = memo( ( { isTransparent, className } ) => { ? filterURLForDisplay( _site?.url ) : _site?.title, }; - }, - [] - ); - const { open: openCommandCenter } = useDispatch( commandsStore ); - - const disableMotion = useReducedMotion(); - const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); - const { clearSelectedBlock } = useDispatch( blockEditorStore ); - const { setDeviceType } = useDispatch( editorStore ); - const isBackToDashboardButton = canvasMode === 'view'; - const siteIconButtonProps = isBackToDashboardButton - ? { - href: dashboardLink, - label: __( 'Go to the Dashboard' ), - } - : { - href: dashboardLink, // We need to keep the `href` here so the component doesn't remount as a ` - + +
- - { canvasMode === 'view' && ( + +
+ +
-
- -
- -