From b7ca80594d4a475cee935e283e80dc778a16295f Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 8 Sep 2023 12:32:06 +0200 Subject: [PATCH 1/6] Remove `gutenberg_should_block_use_interactivity_api` --- lib/experimental/blocks.php | 21 -- packages/block-library/src/file/index.php | 30 ++- .../block-library/src/navigation/index.php | 204 ++++++++---------- 3 files changed, 109 insertions(+), 146 deletions(-) diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index febb404394a4f9..5ab0d093758124 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -5,27 +5,6 @@ * @package gutenberg */ -/** - * Checks whether the experimental Interactivity API should be used for a block. - * - * Note: This function is located here instead of in interactivity-api/blocks.php because it has to be available earler. - * - * @param string $block_name Block name. - * @return bool Whether Interactivity API is used for block. - */ -function gutenberg_should_block_use_interactivity_api( $block_name ) { - - /** - * Filters whether the experimental Interactivity API should be used for a block. - * - * @since 6.3.0 - * - * @param bool $enabled Whether Interactivity API is used for block. - * @param string $block_name Block name. - */ - return (bool) apply_filters( 'gutenberg_should_block_use_interactivity_api', true, $block_name ); -} - if ( ! function_exists( 'wp_enqueue_block_view_script' ) ) { /** * Enqueues a frontend script for a specific block. diff --git a/packages/block-library/src/file/index.php b/packages/block-library/src/file/index.php index 7dd77b20f466c0..17d9fd950c7d82 100644 --- a/packages/block-library/src/file/index.php +++ b/packages/block-library/src/file/index.php @@ -5,23 +5,21 @@ * @package WordPress */ -if ( gutenberg_should_block_use_interactivity_api( 'core/file' ) ) { - /** - * Replaces view script for the File block with version using Interactivity API. - * - * @param array $metadata Block metadata as read in via block.json. - * - * @return array Filtered block type metadata. - */ - function gutenberg_block_core_file_update_interactive_view_script( $metadata ) { - if ( 'core/file' === $metadata['name'] ) { - $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); - $metadata['supports']['interactivity'] = true; - } - return $metadata; +/** + * Replaces view script for the File block with version using Interactivity API. + * + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type metadata. + */ +function gutenberg_block_core_file_update_interactive_view_script( $metadata ) { + if ( 'core/file' === $metadata['name'] ) { + $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); + $metadata['supports']['interactivity'] = true; } - add_filter( 'block_type_metadata', 'gutenberg_block_core_file_update_interactive_view_script', 10, 1 ); + return $metadata; } +add_filter( 'block_type_metadata', 'gutenberg_block_core_file_update_interactive_view_script', 10, 1 ); /** * When the `core/file` block is rendering, check if we need to enqueue the `wp-block-file-view` script. @@ -72,7 +70,7 @@ static function ( $matches ) { ); // If it uses the Interactivity API, add the directives. - if ( gutenberg_should_block_use_interactivity_api( 'core/file' ) && $should_load_view_script ) { + if ( $should_load_view_script ) { $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag(); $processor->set_attribute( 'data-wp-interactive', '' ); diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 52376aba0d44e1..b7bc283773398f 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -67,97 +67,96 @@ function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) { } } -if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) ) { - /** - * Add Interactivity API directives to the navigation-submenu and page-list blocks markup using the Tag Processor - * The final HTML of the navigation-submenu and the page-list blocks will look similar to this: - * - *
  • - * - * Title - * - *
  • - * - * @param string $w Markup of the navigation block. - * @param array $block_attributes Block attributes. - * - * @return string Submenu markup with the directives injected. - */ - function block_core_navigation_add_directives_to_submenu( $w, $block_attributes ) { - while ( $w->next_tag( +/** + * Add Interactivity API directives to the navigation-submenu and page-list blocks markup using the Tag Processor + * The final HTML of the navigation-submenu and the page-list blocks will look similar to this: + * + *
  • + * + * Title + * + *
  • + * + * @param string $w Markup of the navigation block. + * @param array $block_attributes Block attributes. + * + * @return string Submenu markup with the directives injected. + */ +function block_core_navigation_add_directives_to_submenu( $w, $block_attributes ) { + while ( $w->next_tag( + array( + 'tag_name' => 'LI', + 'class_name' => 'has-child', + ) + ) ) { + // Add directives to the parent `
  • `. + $w->set_attribute( 'data-wp-interactive', true ); + $w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "submenuOpenedBy": {}, "type": "submenu" } } }' ); + $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); + $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); + $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); + if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { + $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' ); + $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' ); + } + + // Add directives to the toggle submenu button. + if ( $w->next_tag( array( - 'tag_name' => 'LI', - 'class_name' => 'has-child', + 'tag_name' => 'BUTTON', + 'class_name' => 'wp-block-navigation-submenu__toggle', ) ) ) { - // Add directives to the parent `
  • `. - $w->set_attribute( 'data-wp-interactive', true ); - $w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "submenuOpenedBy": {}, "type": "submenu" } } }' ); - $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); - $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); - $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); - if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { - $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' ); - $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' ); - } + $w->set_attribute( 'data-wp-on--click', 'actions.core.navigation.toggleMenuOnClick' ); + $w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' ); + }; - // Add directives to the toggle submenu button. - if ( $w->next_tag( - array( - 'tag_name' => 'BUTTON', - 'class_name' => 'wp-block-navigation-submenu__toggle', - ) - ) ) { - $w->set_attribute( 'data-wp-on--click', 'actions.core.navigation.toggleMenuOnClick' ); - $w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' ); - }; - - // Add directives to the submenu. - if ( $w->next_tag( - array( - 'tag_name' => 'UL', - 'class_name' => 'wp-block-navigation__submenu-container', - ) - ) ) { - $w->set_attribute( 'data-wp-on--focusin', 'actions.core.navigation.openMenuOnFocus' ); - } - - // Iterate through subitems if exist. - block_core_navigation_add_directives_to_submenu( $w, $block_attributes ); + // Add directives to the submenu. + if ( $w->next_tag( + array( + 'tag_name' => 'UL', + 'class_name' => 'wp-block-navigation__submenu-container', + ) + ) ) { + $w->set_attribute( 'data-wp-on--focusin', 'actions.core.navigation.openMenuOnFocus' ); } - return $w->get_updated_html(); - }; - /** - * Replaces view script for the Navigation block with version using Interactivity API. - * - * @param array $metadata Block metadata as read in via block.json. - * - * @return array Filtered block type metadata. - */ - function gutenberg_block_core_navigation_update_interactive_view_script( $metadata ) { - if ( 'core/navigation' === $metadata['name'] ) { - $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); - $metadata['supports']['interactivity'] = true; - } - return $metadata; + // Iterate through subitems if exist. + block_core_navigation_add_directives_to_submenu( $w, $block_attributes ); + } + return $w->get_updated_html(); +}; + +/** + * Replaces view script for the Navigation block with version using Interactivity API. + * + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type metadata. + */ +function gutenberg_block_core_navigation_update_interactive_view_script( $metadata ) { + if ( 'core/navigation' === $metadata['name'] ) { + $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); + $metadata['supports']['interactivity'] = true; } - add_filter( 'block_type_metadata', 'gutenberg_block_core_navigation_update_interactive_view_script', 10, 1 ); + return $metadata; } +add_filter( 'block_type_metadata', 'gutenberg_block_core_navigation_update_interactive_view_script', 10, 1 ); + /** @@ -671,38 +670,25 @@ function render_block_core_navigation( $attributes, $content, $block ) { $inner_blocks_html .= ''; } - $needed_script_map = array( - 'wp-block-navigation-view' => ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ), - 'wp-block-navigation-view-2' => $is_responsive_menu, - ); - - $should_load_view_script = false; - if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) ) { - // TODO: The script is still loaded even when it isn't needed when the Interactivity API is used. - $should_load_view_script = count( array_filter( $needed_script_map ) ) > 0; - } else { - foreach ( $needed_script_map as $view_script_handle => $is_view_script_needed ) { - - // If the script already exists, there is no point in removing it from viewScript. - if ( wp_script_is( $view_script_handle ) ) { - continue; - } + $should_load_view_script = ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu; + $view_js_file = 'wp-block-navigation-view'; - $script_handles = $block->block_type->view_script_handles; + // If the script already exists, there is no point in removing it from viewScript. + if ( ! wp_script_is( $view_js_file ) ) { + $script_handles = $block->block_type->view_script_handles; - // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( ! $is_view_script_needed && in_array( $view_script_handle, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_script_handle ) ); - } - // If the script is needed, but it was previously removed, add it again. - if ( $is_view_script_needed && ! in_array( $view_script_handle, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_script_handle ) ); - } + // If the script is not needed, and it is still in the `view_script_handles`, remove it. + if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); + } + // If the script is needed, but it was previously removed, add it again. + if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); } } // Add directives to the submenu if needed. - if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) && $has_submenus && $should_load_view_script ) { + if ( $has_submenus && $should_load_view_script ) { $w = new WP_HTML_Tag_Processor( $inner_blocks_html ); $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $w, $attributes ); } @@ -750,7 +736,7 @@ function render_block_core_navigation( $attributes, $content, $block ) { $responsive_container_directives = ''; $responsive_dialog_directives = ''; $close_button_directives = ''; - if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) && $should_load_view_script ) { + if ( $should_load_view_script ) { $nav_element_directives = ' data-wp-interactive data-wp-context=\'{ "core": { "navigation": { "overlayOpenedBy": {}, "type": "overlay", "roleAttribute": "" } } }\' From 9cc03fe4d15c883061e3c731e76a1d1c4d54d885 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 8 Sep 2023 13:05:51 +0200 Subject: [PATCH 2/6] Remove old PHP implementation of the Navigation block --- .../block-library/src/navigation/index.php | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index b7bc283773398f..99b54e560fbfc9 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -141,24 +141,6 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes return $w->get_updated_html(); }; -/** - * Replaces view script for the Navigation block with version using Interactivity API. - * - * @param array $metadata Block metadata as read in via block.json. - * - * @return array Filtered block type metadata. - */ -function gutenberg_block_core_navigation_update_interactive_view_script( $metadata ) { - if ( 'core/navigation' === $metadata['name'] ) { - $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); - $metadata['supports']['interactivity'] = true; - } - return $metadata; -} -add_filter( 'block_type_metadata', 'gutenberg_block_core_navigation_update_interactive_view_script', 10, 1 ); - - - /** * Build an array with CSS classes and inline styles defining the colors * which will be applied to the navigation markup in the front-end. @@ -288,10 +270,6 @@ function block_core_navigation_render_submenu_icon() { return ''; } - - - - /** * Filter out empty "null" blocks from the block list. * 'parse_blocks' includes a null block with '\n\n' as the content when @@ -764,11 +742,11 @@ function render_block_core_navigation( $attributes, $content, $block ) { } $responsive_container_markup = sprintf( - ' + '
    -
    +
    - +
    %2$s
    From a98907787708a0ee0d5679fa77a6e1a928590d2c Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 8 Sep 2023 13:15:30 +0200 Subject: [PATCH 3/6] Remove old JS files of the Navigation block --- package-lock.json | 15 - packages/block-library/package.json | 1 - .../block-library/src/navigation/block.json | 2 +- .../src/navigation/view-interactivity.js | 196 ------------ .../src/navigation/view-modal.js | 127 -------- packages/block-library/src/navigation/view.js | 281 ++++++++++++------ 6 files changed, 186 insertions(+), 436 deletions(-) delete mode 100644 packages/block-library/src/navigation/view-interactivity.js delete mode 100644 packages/block-library/src/navigation/view-modal.js diff --git a/package-lock.json b/package-lock.json index d6cf5b58287218..29d2296841dee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40636,14 +40636,6 @@ "node": ">=8.6" } }, - "node_modules/micromodal": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.10.tgz", - "integrity": "sha512-BUrEnzMPFBwK8nOE4xUDYHLrlGlLULQVjpja99tpJQPSUEWgw3kTLp1n1qv0HmKU29AiHE7Y7sMLiRziDK4ghQ==", - "engines": { - "node": ">=10" - } - }, "node_modules/miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -54750,7 +54742,6 @@ "fast-average-color": "^9.1.1", "fast-deep-equal": "^3.1.3", "memize": "^2.1.0", - "micromodal": "^0.4.10", "remove-accents": "^0.5.0", "uuid": "^8.3.0" }, @@ -67706,7 +67697,6 @@ "fast-average-color": "^9.1.1", "fast-deep-equal": "^3.1.3", "memize": "^2.1.0", - "micromodal": "^0.4.10", "remove-accents": "^0.5.0", "uuid": "^8.3.0" } @@ -89762,11 +89752,6 @@ "picomatch": "^2.3.1" } }, - "micromodal": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.10.tgz", - "integrity": "sha512-BUrEnzMPFBwK8nOE4xUDYHLrlGlLULQVjpja99tpJQPSUEWgw3kTLp1n1qv0HmKU29AiHE7Y7sMLiRziDK4ghQ==" - }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 443c48f8bf4ef8..24b527cb56bf11 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -68,7 +68,6 @@ "fast-average-color": "^9.1.1", "fast-deep-equal": "^3.1.3", "memize": "^2.1.0", - "micromodal": "^0.4.10", "remove-accents": "^0.5.0", "uuid": "^8.3.0" }, diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index e45d0535367786..e5880e8370dcea 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -133,7 +133,7 @@ } } }, - "viewScript": [ "file:./view.min.js", "file:./view-modal.min.js" ], + "viewScript": "file:./view.min.js", "editorStyle": "wp-block-navigation-editor", "style": "wp-block-navigation" } diff --git a/packages/block-library/src/navigation/view-interactivity.js b/packages/block-library/src/navigation/view-interactivity.js deleted file mode 100644 index b0d39ef3ca4d57..00000000000000 --- a/packages/block-library/src/navigation/view-interactivity.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * WordPress dependencies - */ -import { store as wpStore } from '@wordpress/interactivity'; - -const focusableSelectors = [ - 'a[href]', - 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', - 'select:not([disabled]):not([aria-hidden])', - 'textarea:not([disabled]):not([aria-hidden])', - 'button:not([disabled]):not([aria-hidden])', - '[contenteditable]', - '[tabindex]:not([tabindex^="-"])', -]; - -const openMenu = ( store, menuOpenedOn ) => { - const { context, ref, selectors } = store; - selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true; - context.core.navigation.previousFocus = ref; - if ( context.core.navigation.type === 'overlay' ) { - // Add a `has-modal-open` class to the root. - document.documentElement.classList.add( 'has-modal-open' ); - } -}; - -const closeMenu = ( store, menuClosedOn ) => { - const { context, selectors } = store; - selectors.core.navigation.menuOpenedBy( store )[ menuClosedOn ] = false; - // Check if the menu is still open or not. - if ( ! selectors.core.navigation.isMenuOpen( store ) ) { - if ( - context.core.navigation.modal?.contains( - window.document.activeElement - ) - ) { - context.core.navigation.previousFocus.focus(); - } - context.core.navigation.modal = null; - context.core.navigation.previousFocus = null; - if ( context.core.navigation.type === 'overlay' ) { - document.documentElement.classList.remove( 'has-modal-open' ); - } - } -}; - -wpStore( { - effects: { - core: { - navigation: { - initMenu: ( store ) => { - const { context, selectors, ref } = store; - if ( selectors.core.navigation.isMenuOpen( store ) ) { - const focusableElements = - ref.querySelectorAll( focusableSelectors ); - context.core.navigation.modal = ref; - context.core.navigation.firstFocusableElement = - focusableElements[ 0 ]; - context.core.navigation.lastFocusableElement = - focusableElements[ focusableElements.length - 1 ]; - } - }, - focusFirstElement: ( store ) => { - const { selectors, ref } = store; - if ( selectors.core.navigation.isMenuOpen( store ) ) { - ref.querySelector( - '.wp-block-navigation-item > *:first-child' - ).focus(); - } - }, - }, - }, - }, - selectors: { - core: { - navigation: { - roleAttribute: ( store ) => { - const { context, selectors } = store; - return context.core.navigation.type === 'overlay' && - selectors.core.navigation.isMenuOpen( store ) - ? 'dialog' - : ''; - }, - isMenuOpen: ( { context } ) => - // The menu is opened if either `click`, `hover` or `focus` is true. - Object.values( - context.core.navigation[ - context.core.navigation.type === 'overlay' - ? 'overlayOpenedBy' - : 'submenuOpenedBy' - ] - ).filter( Boolean ).length > 0, - menuOpenedBy: ( { context } ) => - context.core.navigation[ - context.core.navigation.type === 'overlay' - ? 'overlayOpenedBy' - : 'submenuOpenedBy' - ], - }, - }, - }, - actions: { - core: { - navigation: { - openMenuOnHover( store ) { - const { navigation } = store.context.core; - if ( - navigation.type === 'submenu' && - // Only open on hover if the overlay is closed. - Object.values( - navigation.overlayOpenedBy || {} - ).filter( Boolean ).length === 0 - ) - openMenu( store, 'hover' ); - }, - closeMenuOnHover( store ) { - closeMenu( store, 'hover' ); - }, - openMenuOnClick( store ) { - openMenu( store, 'click' ); - }, - closeMenuOnClick( store ) { - closeMenu( store, 'click' ); - closeMenu( store, 'focus' ); - }, - openMenuOnFocus( store ) { - openMenu( store, 'focus' ); - }, - toggleMenuOnClick: ( store ) => { - const { selectors } = store; - const menuOpenedBy = - selectors.core.navigation.menuOpenedBy( store ); - if ( menuOpenedBy.click || menuOpenedBy.focus ) { - closeMenu( store, 'click' ); - closeMenu( store, 'focus' ); - } else { - openMenu( store, 'click' ); - } - }, - handleMenuKeydown: ( store ) => { - const { context, selectors, event } = store; - if ( - selectors.core.navigation.menuOpenedBy( store ).click - ) { - // If Escape close the menu. - if ( event?.key === 'Escape' ) { - closeMenu( store, 'click' ); - closeMenu( store, 'focus' ); - return; - } - - // Trap focus if it is an overlay (main menu). - if ( - context.core.navigation.type === 'overlay' && - event.key === 'Tab' - ) { - // If shift + tab it change the direction. - if ( - event.shiftKey && - window.document.activeElement === - context.core.navigation - .firstFocusableElement - ) { - event.preventDefault(); - context.core.navigation.lastFocusableElement.focus(); - } else if ( - ! event.shiftKey && - window.document.activeElement === - context.core.navigation.lastFocusableElement - ) { - event.preventDefault(); - context.core.navigation.firstFocusableElement.focus(); - } - } - } - }, - handleMenuFocusout: ( store ) => { - const { context, event } = 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, - // `window.document.activeElement` doesn't change. - if ( - ! context.core.navigation.modal?.contains( - event.relatedTarget - ) && - event.target !== window.document.activeElement - ) { - closeMenu( store, 'click' ); - closeMenu( store, 'focus' ); - } - }, - }, - }, - }, -} ); diff --git a/packages/block-library/src/navigation/view-modal.js b/packages/block-library/src/navigation/view-modal.js deleted file mode 100644 index 62de6e8808bf0b..00000000000000 --- a/packages/block-library/src/navigation/view-modal.js +++ /dev/null @@ -1,127 +0,0 @@ -/*eslint-env browser*/ -/** - * External dependencies - */ -import MicroModal from 'micromodal'; - -// Responsive navigation toggle. - -/** - * Toggles responsive navigation. - * - * @param {HTMLDivElement} modal - * @param {boolean} isHidden - */ -function navigationToggleModal( modal, isHidden ) { - const dialogContainer = modal.querySelector( - `.wp-block-navigation__responsive-dialog` - ); - - modal.classList.toggle( 'has-modal-open', ! isHidden ); - dialogContainer.toggleAttribute( 'aria-modal', ! isHidden ); - - if ( isHidden ) { - dialogContainer.removeAttribute( 'role' ); - dialogContainer.removeAttribute( 'aria-modal' ); - } else { - dialogContainer.setAttribute( 'role', 'dialog' ); - dialogContainer.setAttribute( 'aria-modal', 'true' ); - } - - // Add a class to indicate the modal is open. - document.documentElement.classList.toggle( 'has-modal-open' ); -} - -/** - * Checks whether the provided link is an anchor on the current page. - * - * @param {HTMLAnchorElement} node - * @return {boolean} Is anchor. - */ -function isLinkToAnchorOnCurrentPage( node ) { - return ( - node.hash && - node.protocol === window.location.protocol && - node.host === window.location.host && - node.pathname === window.location.pathname && - node.search === window.location.search - ); -} - -/** - * Handles effects after opening the modal. - * - * @param {HTMLDivElement} modal - */ -function onShow( modal ) { - navigationToggleModal( modal, false ); - modal.addEventListener( 'click', handleAnchorLinkClicksInsideModal, { - passive: true, - } ); -} - -/** - * Handles effects after closing the modal. - * - * @param {HTMLDivElement} modal - */ -function onClose( modal ) { - navigationToggleModal( modal, true ); - modal.removeEventListener( 'click', handleAnchorLinkClicksInsideModal, { - passive: true, - } ); -} - -/** - * Handle clicks to anchor links in modal using event delegation by closing modal automatically - * - * @param {UIEvent} event - */ -function handleAnchorLinkClicksInsideModal( event ) { - const link = event.target.closest( '.wp-block-navigation-item__content' ); - if ( ! ( link instanceof HTMLAnchorElement ) ) { - return; - } - - // Ignore non-anchor links and anchor links which open on a new tab. - if ( - ! isLinkToAnchorOnCurrentPage( link ) || - link.attributes?.target === '_blank' - ) { - return; - } - - // Find the specific parent modal for this link - // since .close() won't work without an ID if there are - // multiple navigation menus in a post/page. - const modal = link.closest( '.wp-block-navigation__responsive-container' ); - const modalId = modal?.getAttribute( 'id' ); - if ( ! modalId ) { - return; - } - - // check if modal exists and is open before trying to close it - // otherwise Micromodal will toggle the `has-modal-open` class - // on the html tag which prevents scrolling - if ( modalId && modal.classList.contains( 'has-modal-open' ) ) { - MicroModal.close( modalId ); - } -} - -// MicroModal.init() does not support event delegation for the open trigger, so here MicroModal.show() is called manually. -document.addEventListener( - 'click', - ( event ) => { - /** @type {HTMLElement} */ - const target = event.target; - - if ( target.dataset.micromodalTrigger ) { - MicroModal.show( target.dataset.micromodalTrigger, { - onShow, - onClose, - openClass: 'is-menu-open', - } ); - } - }, - { passive: true } -); diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index d808d1707d5bfe..b0d39ef3ca4d57 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -1,107 +1,196 @@ -/*eslint-env browser*/ -// Open on click functionality. - -/** - * Keep track of whether a submenu is open to short-circuit delegated event listeners. - * - * @type {boolean} - */ -let hasOpenSubmenu = false; - -/** - * Close submenu items for a navigation item. - * - * @param {HTMLElement} navigationItem - Either a NAV or LI element. - */ -function closeSubmenus( navigationItem ) { - navigationItem - .querySelectorAll( '[aria-expanded="true"]' ) - .forEach( function ( toggle ) { - toggle.setAttribute( 'aria-expanded', 'false' ); - } ); - hasOpenSubmenu = false; -} - /** - * Toggle submenu on click. - * - * @param {HTMLButtonElement} buttonToggle + * WordPress dependencies */ -function toggleSubmenuOnClick( buttonToggle ) { - const isSubmenuOpen = - buttonToggle.getAttribute( 'aria-expanded' ) === 'true'; - const navigationItem = buttonToggle.closest( '.wp-block-navigation-item' ); +import { store as wpStore } from '@wordpress/interactivity'; - if ( isSubmenuOpen ) { - closeSubmenus( navigationItem ); - } else { - // Close all sibling submenus. - const navigationParent = buttonToggle.closest( - '.wp-block-navigation__submenu-container, .wp-block-navigation__container, .wp-block-page-list' - ); - navigationParent - .querySelectorAll( '.wp-block-navigation-item' ) - .forEach( ( child ) => { - if ( child !== navigationItem ) { - closeSubmenus( child ); - } - } ); +const focusableSelectors = [ + 'a[href]', + 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', + 'select:not([disabled]):not([aria-hidden])', + 'textarea:not([disabled]):not([aria-hidden])', + 'button:not([disabled]):not([aria-hidden])', + '[contenteditable]', + '[tabindex]:not([tabindex^="-"])', +]; - // Open submenu. - buttonToggle.setAttribute( 'aria-expanded', 'true' ); - hasOpenSubmenu = true; +const openMenu = ( store, menuOpenedOn ) => { + const { context, ref, selectors } = store; + selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true; + context.core.navigation.previousFocus = ref; + if ( context.core.navigation.type === 'overlay' ) { + // Add a `has-modal-open` class to the root. + document.documentElement.classList.add( 'has-modal-open' ); } -} +}; -// Open on button click or close on click outside. -document.addEventListener( - 'click', - function ( event ) { - const target = event.target; - const button = target.closest( '.wp-block-navigation-submenu__toggle' ); - - // Close any other open submenus. - if ( hasOpenSubmenu ) { - const navigationBlocks = document.querySelectorAll( - '.wp-block-navigation' - ); - navigationBlocks.forEach( function ( block ) { - if ( ! block.contains( target ) ) { - closeSubmenus( block ); - } - } ); +const closeMenu = ( store, menuClosedOn ) => { + const { context, selectors } = store; + selectors.core.navigation.menuOpenedBy( store )[ menuClosedOn ] = false; + // Check if the menu is still open or not. + if ( ! selectors.core.navigation.isMenuOpen( store ) ) { + if ( + context.core.navigation.modal?.contains( + window.document.activeElement + ) + ) { + context.core.navigation.previousFocus.focus(); } - - // Now open the submenu if one was clicked. - if ( button instanceof HTMLButtonElement ) { - toggleSubmenuOnClick( button ); + context.core.navigation.modal = null; + context.core.navigation.previousFocus = null; + if ( context.core.navigation.type === 'overlay' ) { + document.documentElement.classList.remove( 'has-modal-open' ); } - }, - { passive: true } -); + } +}; -// Close on focus outside or escape key. -document.addEventListener( - 'keyup', - function ( event ) { - // Abort if there aren't any submenus open anyway. - if ( ! hasOpenSubmenu ) { - return; - } +wpStore( { + effects: { + core: { + navigation: { + initMenu: ( store ) => { + const { context, selectors, ref } = store; + if ( selectors.core.navigation.isMenuOpen( store ) ) { + const focusableElements = + ref.querySelectorAll( focusableSelectors ); + context.core.navigation.modal = ref; + context.core.navigation.firstFocusableElement = + focusableElements[ 0 ]; + context.core.navigation.lastFocusableElement = + focusableElements[ focusableElements.length - 1 ]; + } + }, + focusFirstElement: ( store ) => { + const { selectors, ref } = store; + if ( selectors.core.navigation.isMenuOpen( store ) ) { + ref.querySelector( + '.wp-block-navigation-item > *:first-child' + ).focus(); + } + }, + }, + }, + }, + selectors: { + core: { + navigation: { + roleAttribute: ( store ) => { + const { context, selectors } = store; + return context.core.navigation.type === 'overlay' && + selectors.core.navigation.isMenuOpen( store ) + ? 'dialog' + : ''; + }, + isMenuOpen: ( { context } ) => + // The menu is opened if either `click`, `hover` or `focus` is true. + Object.values( + context.core.navigation[ + context.core.navigation.type === 'overlay' + ? 'overlayOpenedBy' + : 'submenuOpenedBy' + ] + ).filter( Boolean ).length > 0, + menuOpenedBy: ( { context } ) => + context.core.navigation[ + context.core.navigation.type === 'overlay' + ? 'overlayOpenedBy' + : 'submenuOpenedBy' + ], + }, + }, + }, + actions: { + core: { + navigation: { + openMenuOnHover( store ) { + const { navigation } = store.context.core; + if ( + navigation.type === 'submenu' && + // Only open on hover if the overlay is closed. + Object.values( + navigation.overlayOpenedBy || {} + ).filter( Boolean ).length === 0 + ) + openMenu( store, 'hover' ); + }, + closeMenuOnHover( store ) { + closeMenu( store, 'hover' ); + }, + openMenuOnClick( store ) { + openMenu( store, 'click' ); + }, + closeMenuOnClick( store ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + }, + openMenuOnFocus( store ) { + openMenu( store, 'focus' ); + }, + toggleMenuOnClick: ( store ) => { + const { selectors } = store; + const menuOpenedBy = + selectors.core.navigation.menuOpenedBy( store ); + if ( menuOpenedBy.click || menuOpenedBy.focus ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + } else { + openMenu( store, 'click' ); + } + }, + handleMenuKeydown: ( store ) => { + const { context, selectors, event } = store; + if ( + selectors.core.navigation.menuOpenedBy( store ).click + ) { + // If Escape close the menu. + if ( event?.key === 'Escape' ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + return; + } - const submenuBlocks = document.querySelectorAll( - '.wp-block-navigation-item.has-child' - ); - submenuBlocks.forEach( function ( block ) { - if ( ! block.contains( event.target ) ) { - closeSubmenus( block ); - } else if ( event.key === 'Escape' ) { - const toggle = block.querySelector( '[aria-expanded="true"]' ); - closeSubmenus( block ); - // Focus the submenu trigger so focus does not get trapped in the closed submenu. - toggle?.focus(); - } - } ); + // Trap focus if it is an overlay (main menu). + if ( + context.core.navigation.type === 'overlay' && + event.key === 'Tab' + ) { + // If shift + tab it change the direction. + if ( + event.shiftKey && + window.document.activeElement === + context.core.navigation + .firstFocusableElement + ) { + event.preventDefault(); + context.core.navigation.lastFocusableElement.focus(); + } else if ( + ! event.shiftKey && + window.document.activeElement === + context.core.navigation.lastFocusableElement + ) { + event.preventDefault(); + context.core.navigation.firstFocusableElement.focus(); + } + } + } + }, + handleMenuFocusout: ( store ) => { + const { context, event } = 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, + // `window.document.activeElement` doesn't change. + if ( + ! context.core.navigation.modal?.contains( + event.relatedTarget + ) && + event.target !== window.document.activeElement + ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + } + }, + }, + }, }, - { passive: true } -); +} ); From 441c3aeb76e41c45a639869c9d43ebf74f4931d9 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 8 Sep 2023 13:15:58 +0200 Subject: [PATCH 4/6] Remove old implementation of the File block --- packages/block-library/src/file/index.php | 16 ---------------- .../block-library/src/file/utils/index.js | 15 --------------- .../src/file/view-interactivity.js | 18 ------------------ packages/block-library/src/file/view.js | 19 ++++++++++++++----- 4 files changed, 14 insertions(+), 54 deletions(-) delete mode 100644 packages/block-library/src/file/view-interactivity.js diff --git a/packages/block-library/src/file/index.php b/packages/block-library/src/file/index.php index 17d9fd950c7d82..212cb571e99f7f 100644 --- a/packages/block-library/src/file/index.php +++ b/packages/block-library/src/file/index.php @@ -5,22 +5,6 @@ * @package WordPress */ -/** - * Replaces view script for the File block with version using Interactivity API. - * - * @param array $metadata Block metadata as read in via block.json. - * - * @return array Filtered block type metadata. - */ -function gutenberg_block_core_file_update_interactive_view_script( $metadata ) { - if ( 'core/file' === $metadata['name'] ) { - $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); - $metadata['supports']['interactivity'] = true; - } - return $metadata; -} -add_filter( 'block_type_metadata', 'gutenberg_block_core_file_update_interactive_view_script', 10, 1 ); - /** * When the `core/file` block is rendering, check if we need to enqueue the `wp-block-file-view` script. * diff --git a/packages/block-library/src/file/utils/index.js b/packages/block-library/src/file/utils/index.js index e1eed7a2d2808e..a60e9e131c4e45 100644 --- a/packages/block-library/src/file/utils/index.js +++ b/packages/block-library/src/file/utils/index.js @@ -54,18 +54,3 @@ const createActiveXObject = ( type ) => { } return ax; }; - -/** - * Hides all .wp-block-file__embed elements on the document. This function is only intended - * to be run on the front-end, it may have weird side effects running in the block editor. - */ -export const hidePdfEmbedsOnUnsupportedBrowsers = () => { - if ( ! browserSupportsPdfs() ) { - const embeds = document.getElementsByClassName( - 'wp-block-file__embed' - ); - Array.from( embeds ).forEach( ( embed ) => { - embed.style.display = 'none'; - } ); - } -}; diff --git a/packages/block-library/src/file/view-interactivity.js b/packages/block-library/src/file/view-interactivity.js deleted file mode 100644 index 9d09ca2b7f4340..00000000000000 --- a/packages/block-library/src/file/view-interactivity.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * WordPress dependencies - */ -import { store } from '@wordpress/interactivity'; -/** - * Internal dependencies - */ -import { browserSupportsPdfs as hasPdfPreview } from './utils'; - -store( { - selectors: { - core: { - file: { - hasPdfPreview, - }, - }, - }, -} ); diff --git a/packages/block-library/src/file/view.js b/packages/block-library/src/file/view.js index 6d0b61fa51cb7c..9d09ca2b7f4340 100644 --- a/packages/block-library/src/file/view.js +++ b/packages/block-library/src/file/view.js @@ -1,9 +1,18 @@ +/** + * WordPress dependencies + */ +import { store } from '@wordpress/interactivity'; /** * Internal dependencies */ -import { hidePdfEmbedsOnUnsupportedBrowsers } from './utils'; +import { browserSupportsPdfs as hasPdfPreview } from './utils'; -document.addEventListener( - 'DOMContentLoaded', - hidePdfEmbedsOnUnsupportedBrowsers -); +store( { + selectors: { + core: { + file: { + hasPdfPreview, + }, + }, + }, +} ); From 18a11c0cbb937ff72845dd1a5e3cfdf64984fbe4 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 8 Sep 2023 14:18:55 +0200 Subject: [PATCH 5/6] Add `supports.interactivity` back --- docs/reference-guides/core-blocks.md | 4 ++-- packages/block-library/src/file/block.json | 3 ++- packages/block-library/src/navigation/block.json | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index b8a5390aebf549..e15f321b7b4204 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -266,7 +266,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb - **Name:** core/file - **Category:** media -- **Supports:** align, anchor, color (background, gradients, link, ~~text~~), spacing (margin, padding) +- **Supports:** align, anchor, color (background, gradients, link, ~~text~~), interactivity, spacing (margin, padding) - **Attributes:** displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget ## Footnotes @@ -421,7 +421,7 @@ A collection of blocks that allow visitors to get around your site. ([Source](ht - **Name:** core/navigation - **Category:** theme -- **Supports:** align (full, wide), inserter, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), inserter, interactivity, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** __unstableLocation, backgroundColor, customBackgroundColor, customOverlayBackgroundColor, customOverlayTextColor, customTextColor, hasIcon, icon, maxNestingLevel, openSubmenusOnClick, overlayBackgroundColor, overlayMenu, overlayTextColor, ref, rgbBackgroundColor, rgbTextColor, showSubmenuIcon, templateLock, textColor ## Custom Link diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index 576fe34f5cf8f7..0cc20b3f501e9b 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -69,7 +69,8 @@ "background": true, "link": true } - } + }, + "interactivity": true }, "viewScript": "file:./view.min.js", "editorStyle": "wp-block-file-editor", diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index e5880e8370dcea..7896ea147699f7 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -131,7 +131,8 @@ } } } - } + }, + "interactivity": true }, "viewScript": "file:./view.min.js", "editorStyle": "wp-block-navigation-editor", From 9a1eb73b8262023de3bc0f54f9593d70263af939 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 8 Sep 2023 15:28:24 +0200 Subject: [PATCH 6/6] Remove directives example --- .../block-library/src/navigation/index.php | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 99b54e560fbfc9..addc55f728715d 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -69,27 +69,8 @@ function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) { /** - * Add Interactivity API directives to the navigation-submenu and page-list blocks markup using the Tag Processor - * The final HTML of the navigation-submenu and the page-list blocks will look similar to this: - * - *
  • - * - * Title - * - *
  • + * Add Interactivity API directives to the navigation-submenu and page-list + * blocks markup using the Tag Processor. * * @param string $w Markup of the navigation block. * @param array $block_attributes Block attributes.