-
+
+
%2$s
@@ -621,13 +798,18 @@ function render_block_core_navigation( $attributes, $content, $block ) {
esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ),
__( 'Menu' ),
$toggle_button_content,
- $toggle_close_button_content
+ $toggle_close_button_content,
+ $open_button_directives,
+ $responsive_container_directives,
+ $responsive_dialog_directives,
+ $close_button_directives
);
return sprintf(
- '
',
+ '
',
$wrapper_attributes,
- $responsive_container_markup
+ $responsive_container_markup,
+ $nav_element_directives
);
}
@@ -685,6 +867,8 @@ function block_core_navigation_typographic_presets_backcompatibility( $parsed_bl
/**
* Turns menu item data into a nested array of parsed blocks
*
+ * @deprecated 6.3.0 Use WP_Navigation_Fallback::parse_blocks_from_menu_items() instead.
+ *
* @param array $menu_items An array of menu items that represent
* an individual level of a menu.
* @param array $menu_items_by_parent_id An array keyed by the id of the
@@ -695,7 +879,7 @@ function block_core_navigation_typographic_presets_backcompatibility( $parsed_bl
*/
function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
- _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback_Gutenberg::parse_blocks_from_menu_items' );
+ _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::parse_blocks_from_menu_items' );
if ( empty( $menu_items ) ) {
return array();
@@ -740,11 +924,13 @@ function block_core_navigation_parse_blocks_from_menu_items( $menu_items, $menu_
/**
* Get the classic navigation menu to use as a fallback.
*
+ * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback() instead.
+ *
* @return object WP_Term The classic navigation.
*/
function block_core_navigation_get_classic_menu_fallback() {
- _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback_Gutenberg::get_classic_menu_fallback' );
+ _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback' );
$classic_nav_menus = wp_get_nav_menus();
@@ -771,7 +957,7 @@ function block_core_navigation_get_classic_menu_fallback() {
// Otherwise return the most recently created classic menu.
usort(
$classic_nav_menus,
- function( $a, $b ) {
+ static function( $a, $b ) {
return $b->term_id - $a->term_id;
}
);
@@ -782,12 +968,14 @@ function( $a, $b ) {
/**
* Converts a classic navigation to blocks.
*
+ * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_classic_menu_fallback_blocks() instead.
+ *
* @param object $classic_nav_menu WP_Term The classic navigation object to convert.
* @return array the normalized parsed blocks.
*/
function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
- _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback_Gutenberg::get_classic_menu_fallback_blocks' );
+ _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_classic_menu_fallback_blocks' );
// BEGIN: Code that already exists in wp_nav_menu().
$menu_items = wp_get_nav_menu_items( $classic_nav_menu->term_id, array( 'update_post_term_cache' => false ) );
@@ -820,13 +1008,15 @@ function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_me
}
/**
- * If there's a the classic menu then use it as a fallback.
+ * If there's a classic menu then use it as a fallback.
+ *
+ * @deprecated 6.3.0 Use WP_Navigation_Fallback::create_classic_menu_fallback() instead.
*
* @return array the normalized parsed blocks.
*/
function block_core_navigation_maybe_use_classic_menu_fallback() {
- _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback_Gutenberg::create_classic_menu_fallback' );
+ _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::create_classic_menu_fallback' );
// See if we have a classic menu.
$classic_nav_menu = block_core_navigation_get_classic_menu_fallback();
@@ -865,11 +1055,13 @@ function block_core_navigation_maybe_use_classic_menu_fallback() {
/**
* Finds the most recently published `wp_navigation` Post.
*
+ * @deprecated 6.3.0 Use WP_Navigation_Fallback::get_most_recently_published_navigation() instead.
+ *
* @return WP_Post|null the first non-empty Navigation or null.
*/
function block_core_navigation_get_most_recently_published_navigation() {
- _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback_Gutenberg::get_most_recently_published_navigation' );
+ _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Navigation_Fallback::get_most_recently_published_navigation' );
// Default to the most recently created menu.
$parsed_args = array(
diff --git a/packages/block-library/src/navigation/interactivity.js b/packages/block-library/src/navigation/interactivity.js
deleted file mode 100644
index 80152762c9cd63..00000000000000
--- a/packages/block-library/src/navigation/interactivity.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * Internal dependencies
- */
-import { store } from '../utils/interactivity';
-
-const focusableSelectors = [
- 'a[href]',
- 'area[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])',
- 'iframe',
- 'object',
- 'embed',
- '[contenteditable]',
- '[tabindex]:not([tabindex^="-"])',
-];
-
-store( {
- effects: {
- core: {
- navigation: {
- initMenu: ( { context, ref } ) => {
- if ( context.core.navigation.isMenuOpen ) {
- 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: ( { context, ref } ) => {
- if ( context.core.navigation.isMenuOpen ) {
- ref.querySelector(
- '.wp-block-navigation-item > *:first-child'
- ).focus();
- }
- },
- },
- },
- },
- selectors: {
- core: {
- navigation: {
- roleAttribute: ( { context } ) => {
- return context.core.navigation.overlay &&
- context.core.navigation.isMenuOpen
- ? 'dialog'
- : '';
- },
- },
- },
- },
- actions: {
- core: {
- navigation: {
- openMenu: ( { context, ref } ) => {
- context.core.navigation.isMenuOpen = true;
- context.core.navigation.previousFocus = ref;
- if ( context.core.navigation.overlay ) {
- // It adds a `has-modal-open` class to the root
- document.documentElement.classList.add(
- 'has-modal-open'
- );
- }
- },
- closeMenu: ( { context } ) => {
- if ( context.core.navigation.isMenuOpen ) {
- context.core.navigation.isMenuOpen = false;
- 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.overlay ) {
- document.documentElement.classList.remove(
- 'has-modal-open'
- );
- }
- }
- },
- toggleMenu: ( { context, actions, ref } ) => {
- if ( context.core.navigation.isMenuOpen ) {
- actions.core.navigation.closeMenu( { context } );
- } else {
- actions.core.navigation.openMenu( { context, ref } );
- }
- },
- handleMenuKeydown: ( { actions, context, event } ) => {
- if ( context.core.navigation.isMenuOpen ) {
- // If Escape close the menu
- if (
- event?.key === 'Escape' ||
- event?.keyCode === 27
- ) {
- actions.core.navigation.closeMenu( { context } );
- return;
- }
-
- // Trap focus if it is an overlay (main menu)
- if (
- context.core.navigation.overlay &&
- ( event.key === 'Tab' || event.keyCode === 9 )
- ) {
- // 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: ( { actions, context, event } ) => {
- if ( context.core.navigation.isMenuOpen ) {
- // 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
- ) {
- actions.core.navigation.closeMenu( { context } );
- }
- }
- },
- },
- },
- },
-} );
diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss
index b0c8748075bdd7..180b40b43daca1 100644
--- a/packages/block-library/src/navigation/style.scss
+++ b/packages/block-library/src/navigation/style.scss
@@ -31,6 +31,7 @@ $navigation-icon-size: 24px;
// Menu item container.
.wp-block-navigation-item {
+ background-color: inherit;
display: flex;
align-items: center;
position: relative;
@@ -398,15 +399,26 @@ button.wp-block-navigation-item__content {
}
// Default background and font color.
-.wp-block-navigation:not(.has-background) {
+.wp-block-navigation:not(.has-background) .wp-block-navigation__submenu-container {
+ // Set a background color for submenus so that they're not transparent.
+ // NOTE TO DEVS - if refactoring this code, please double-check that
+ // submenus have a default background color, this feature has regressed
+ // several times, so care needs to be taken.
+ background-color: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+}
+
+// If we do have a background color selected, inherit it from the navigation block
+.wp-block-navigation.has-background {
.wp-block-navigation__submenu-container {
- // Set a background color for submenus so that they're not transparent.
- // NOTE TO DEVS - if refactoring this code, please double-check that
- // submenus have a default background color, this feature has regressed
- // several times, so care needs to be taken.
- background-color: #fff;
+ background-color: inherit;
+ }
+}
+
+.wp-block-navigation:not(.has-text-color) {
+ .wp-block-navigation__submenu-container {
+ // Set a default color for submenu text if none is selected
color: #000;
- border: 1px solid rgba(0, 0, 0, 0.15);
}
}
@@ -458,7 +470,8 @@ button.wp-block-navigation-item__content {
right: 0;
bottom: 0;
- .wp-block-navigation-link a {
+ // Low specificity so that themes can override.
+ & :where(.wp-block-navigation-item a) {
color: inherit;
}
@@ -579,6 +592,7 @@ button.wp-block-navigation-item__content {
// Remove background colors for items inside the overlay menu.
// Has to be !important to override global styles.
.wp-block-navigation-item .wp-block-navigation__submenu-container,
+ .wp-block-navigation__container,
.wp-block-navigation-item,
.wp-block-page-list {
color: inherit !important;
@@ -620,9 +634,14 @@ button.wp-block-navigation-item__content {
.wp-block-navigation:not(.has-background)
.wp-block-navigation__responsive-container.is-menu-open {
background-color: #fff;
+}
+
+.wp-block-navigation:not(.has-text-color)
+.wp-block-navigation__responsive-container.is-menu-open {
color: #000;
}
+
// Overlay menu toggle button label
.wp-block-navigation__toggle_button_label {
font-size: 1rem;
diff --git a/packages/block-library/src/navigation/test/use-navigation-menu.js b/packages/block-library/src/navigation/test/use-navigation-menu.js
index b03776b663eaf8..f82acca835884d 100644
--- a/packages/block-library/src/navigation/test/use-navigation-menu.js
+++ b/packages/block-library/src/navigation/test/use-navigation-menu.js
@@ -36,16 +36,28 @@ function resolveRecords( registry, menus ) {
dispatch.startResolution( 'getEntityRecords', [
'postType',
'wp_navigation',
- { per_page: -1, status: [ 'publish', 'draft' ] },
+ {
+ per_page: 100,
+ status: [ 'publish', 'draft' ],
+ order: 'desc',
+ orderby: 'date',
+ },
] );
dispatch.finishResolution( 'getEntityRecords', [
'postType',
'wp_navigation',
- { per_page: -1, status: [ 'publish', 'draft' ] },
+ {
+ per_page: 100,
+ status: [ 'publish', 'draft' ],
+ order: 'desc',
+ orderby: 'date',
+ },
] );
dispatch.receiveEntityRecords( 'postType', 'wp_navigation', menus, {
- per_page: -1,
+ per_page: 100,
status: [ 'publish', 'draft' ],
+ order: 'desc',
+ orderby: 'date',
} );
}
diff --git a/packages/block-library/src/navigation/use-navigation-menu.js b/packages/block-library/src/navigation/use-navigation-menu.js
index 607a92cb82f954..02df0ba2831dcc 100644
--- a/packages/block-library/src/navigation/use-navigation-menu.js
+++ b/packages/block-library/src/navigation/use-navigation-menu.js
@@ -4,82 +4,61 @@
import {
store as coreStore,
useResourcePermissions,
+ useEntityRecords,
} from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
+/**
+ * Internal dependencies
+ */
+import { PRELOADED_NAVIGATION_MENUS_QUERY } from './constants';
+
export default function useNavigationMenu( ref ) {
const permissions = useResourcePermissions( 'navigation', ref );
- return useSelect(
+ const {
+ navigationMenu,
+ isNavigationMenuResolved,
+ isNavigationMenuMissing,
+ } = useSelect(
( select ) => {
- const {
- canCreate,
- canUpdate,
- canDelete,
- isResolving,
- hasResolved,
- } = permissions;
-
- const {
- navigationMenus,
- isResolvingNavigationMenus,
- hasResolvedNavigationMenus,
- } = selectNavigationMenus( select );
-
- const {
- navigationMenu,
- isNavigationMenuResolved,
- isNavigationMenuMissing,
- } = selectExistingMenu( select, ref );
-
- return {
- navigationMenus,
- isResolvingNavigationMenus,
- hasResolvedNavigationMenus,
-
- navigationMenu,
- isNavigationMenuResolved,
- isNavigationMenuMissing,
-
- canSwitchNavigationMenu: ref
- ? navigationMenus?.length > 1
- : navigationMenus?.length > 0,
-
- canUserCreateNavigationMenu: canCreate,
- isResolvingCanUserCreateNavigationMenu: isResolving,
- hasResolvedCanUserCreateNavigationMenu: hasResolved,
-
- canUserUpdateNavigationMenu: canUpdate,
- hasResolvedCanUserUpdateNavigationMenu: ref
- ? hasResolved
- : undefined,
-
- canUserDeleteNavigationMenu: canDelete,
- hasResolvedCanUserDeleteNavigationMenu: ref
- ? hasResolved
- : undefined,
- };
+ return selectExistingMenu( select, ref );
},
- [ ref, permissions ]
+ [ ref ]
);
-}
-function selectNavigationMenus( select ) {
- const { getEntityRecords, hasFinishedResolution, isResolving } =
- select( coreStore );
+ const { canCreate, canUpdate, canDelete, isResolving, hasResolved } =
+ permissions;
- const args = [
+ const {
+ records: navigationMenus,
+ isResolving: isResolvingNavigationMenus,
+ hasResolved: hasResolvedNavigationMenus,
+ } = useEntityRecords(
'postType',
- 'wp_navigation',
- { per_page: -1, status: [ 'publish', 'draft' ] },
- ];
+ `wp_navigation`,
+ PRELOADED_NAVIGATION_MENUS_QUERY
+ );
+
+ const canSwitchNavigationMenu = ref
+ ? navigationMenus?.length > 1
+ : navigationMenus?.length > 0;
+
return {
- navigationMenus: getEntityRecords( ...args ),
- isResolvingNavigationMenus: isResolving( 'getEntityRecords', args ),
- hasResolvedNavigationMenus: hasFinishedResolution(
- 'getEntityRecords',
- args
- ),
+ navigationMenu,
+ isNavigationMenuResolved,
+ isNavigationMenuMissing,
+ navigationMenus,
+ isResolvingNavigationMenus,
+ hasResolvedNavigationMenus,
+ canSwitchNavigationMenu,
+ canUserCreateNavigationMenu: canCreate,
+ isResolvingCanUserCreateNavigationMenu: isResolving,
+ hasResolvedCanUserCreateNavigationMenu: hasResolved,
+ canUserUpdateNavigationMenu: canUpdate,
+ hasResolvedCanUserUpdateNavigationMenu: ref ? hasResolved : undefined,
+ canUserDeleteNavigationMenu: canDelete,
+ hasResolvedCanUserDeleteNavigationMenu: ref ? hasResolved : undefined,
};
}
diff --git a/packages/block-library/src/navigation/view-interactivity.js b/packages/block-library/src/navigation/view-interactivity.js
new file mode 100644
index 00000000000000..b0d39ef3ca4d57
--- /dev/null
+++ b/packages/block-library/src/navigation/view-interactivity.js
@@ -0,0 +1,196 @@
+/**
+ * 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
index 9477d262816d93..62de6e8808bf0b 100644
--- a/packages/block-library/src/navigation/view-modal.js
+++ b/packages/block-library/src/navigation/view-modal.js
@@ -1,16 +1,22 @@
+/*eslint-env browser*/
/**
* External dependencies
*/
import MicroModal from 'micromodal';
// Responsive navigation toggle.
-function navigationToggleModal( modal ) {
+
+/**
+ * Toggles responsive navigation.
+ *
+ * @param {HTMLDivElement} modal
+ * @param {boolean} isHidden
+ */
+function navigationToggleModal( modal, isHidden ) {
const dialogContainer = modal.querySelector(
`.wp-block-navigation__responsive-dialog`
);
- const isHidden = 'true' === modal.getAttribute( 'aria-hidden' );
-
modal.classList.toggle( 'has-modal-open', ! isHidden );
dialogContainer.toggleAttribute( 'aria-modal', ! isHidden );
@@ -23,10 +29,15 @@ function navigationToggleModal( modal ) {
}
// Add a class to indicate the modal is open.
- const htmlElement = document.documentElement;
- htmlElement.classList.toggle( 'has-modal-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 &&
@@ -37,42 +48,80 @@ function isLinkToAnchorOnCurrentPage( node ) {
);
}
-window.addEventListener( 'load', () => {
- MicroModal.init( {
- onShow: navigationToggleModal,
- onClose: navigationToggleModal,
- openClass: 'is-menu-open',
+/**
+ * Handles effects after opening the modal.
+ *
+ * @param {HTMLDivElement} modal
+ */
+function onShow( modal ) {
+ navigationToggleModal( modal, false );
+ modal.addEventListener( 'click', handleAnchorLinkClicksInsideModal, {
+ passive: true,
} );
+}
- // Close modal automatically on clicking anchor links inside modal.
- const navigationLinks = document.querySelectorAll(
- '.wp-block-navigation-item__content'
- );
+/**
+ * Handles effects after closing the modal.
+ *
+ * @param {HTMLDivElement} modal
+ */
+function onClose( modal ) {
+ navigationToggleModal( modal, true );
+ modal.removeEventListener( 'click', handleAnchorLinkClicksInsideModal, {
+ passive: true,
+ } );
+}
- navigationLinks.forEach( function ( link ) {
- // Ignore non-anchor links and anchor links which open on a new tab.
- if (
- ! isLinkToAnchorOnCurrentPage( link ) ||
- link.attributes?.target === '_blank'
- ) {
- return;
- }
+/**
+ * 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;
+ }
- // 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' );
+ // Ignore non-anchor links and anchor links which open on a new tab.
+ if (
+ ! isLinkToAnchorOnCurrentPage( link ) ||
+ link.attributes?.target === '_blank'
+ ) {
+ return;
+ }
- link.addEventListener( 'click', () => {
- // 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 );
- }
- } );
- } );
-} );
+ // 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 19805a44ae4ae2..d808d1707d5bfe 100644
--- a/packages/block-library/src/navigation/view.js
+++ b/packages/block-library/src/navigation/view.js
@@ -1,62 +1,94 @@
+/*eslint-env browser*/
// Open on click functionality.
-function closeSubmenus( element ) {
- element
+
+/**
+ * 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;
}
-function toggleSubmenuOnClick( event ) {
- const buttonToggle = event.target.closest( '[aria-expanded]' );
- const isSubmenuOpen = buttonToggle.getAttribute( 'aria-expanded' );
+/**
+ * Toggle submenu on click.
+ *
+ * @param {HTMLButtonElement} buttonToggle
+ */
+function toggleSubmenuOnClick( buttonToggle ) {
+ const isSubmenuOpen =
+ buttonToggle.getAttribute( 'aria-expanded' ) === 'true';
+ const navigationItem = buttonToggle.closest( '.wp-block-navigation-item' );
- if ( isSubmenuOpen === 'true' ) {
- closeSubmenus( buttonToggle.closest( '.wp-block-navigation-item' ) );
+ if ( isSubmenuOpen ) {
+ closeSubmenus( navigationItem );
} else {
// Close all sibling submenus.
- const parentElement = buttonToggle.closest(
- '.wp-block-navigation-item'
- );
const navigationParent = buttonToggle.closest(
'.wp-block-navigation__submenu-container, .wp-block-navigation__container, .wp-block-page-list'
);
navigationParent
.querySelectorAll( '.wp-block-navigation-item' )
- .forEach( function ( child ) {
- if ( child !== parentElement ) {
+ .forEach( ( child ) => {
+ if ( child !== navigationItem ) {
closeSubmenus( child );
}
} );
+
// Open submenu.
buttonToggle.setAttribute( 'aria-expanded', 'true' );
+ hasOpenSubmenu = true;
}
}
-// Necessary for some themes such as TT1 Blocks, where
-// scripts could be loaded before the body.
-window.addEventListener( 'load', () => {
- const submenuButtons = document.querySelectorAll(
- '.wp-block-navigation-submenu__toggle'
- );
+// 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' );
- submenuButtons.forEach( function ( button ) {
- button.addEventListener( 'click', toggleSubmenuOnClick );
- } );
+ // Close any other open submenus.
+ if ( hasOpenSubmenu ) {
+ const navigationBlocks = document.querySelectorAll(
+ '.wp-block-navigation'
+ );
+ navigationBlocks.forEach( function ( block ) {
+ if ( ! block.contains( target ) ) {
+ closeSubmenus( block );
+ }
+ } );
+ }
+
+ // Now open the submenu if one was clicked.
+ if ( button instanceof HTMLButtonElement ) {
+ toggleSubmenuOnClick( button );
+ }
+ },
+ { 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;
+ }
- // Close on click outside.
- document.addEventListener( 'click', function ( event ) {
- const navigationBlocks = document.querySelectorAll(
- '.wp-block-navigation'
- );
- navigationBlocks.forEach( function ( block ) {
- if ( ! block.contains( event.target ) ) {
- closeSubmenus( block );
- }
- } );
- } );
- // Close on focus outside or escape key.
- document.addEventListener( 'keyup', function ( event ) {
const submenuBlocks = document.querySelectorAll(
'.wp-block-navigation-item.has-child'
);
@@ -70,5 +102,6 @@ window.addEventListener( 'load', () => {
toggle?.focus();
}
} );
- } );
-} );
+ },
+ { passive: true }
+);
diff --git a/packages/block-library/src/nextpage/block.json b/packages/block-library/src/nextpage/block.json
index 6a133264d6747c..ab88d4a7be4f0b 100644
--- a/packages/block-library/src/nextpage/block.json
+++ b/packages/block-library/src/nextpage/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/nextpage",
"title": "Page Break",
"category": "design",
diff --git a/packages/block-library/src/nextpage/edit.native.js b/packages/block-library/src/nextpage/edit.native.js
index 8570b942fd60a6..508ca7e10553a3 100644
--- a/packages/block-library/src/nextpage/edit.native.js
+++ b/packages/block-library/src/nextpage/edit.native.js
@@ -2,13 +2,13 @@
* External dependencies
*/
import { View } from 'react-native';
-import Hr from 'react-native-hr';
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { withPreferredColorScheme } from '@wordpress/compose';
+import { HorizontalRule } from '@wordpress/components';
/**
* Internal dependencies
@@ -44,7 +44,7 @@ export function NextPageEdit( {
accessibilityStates={ accessibilityState }
onAccessibilityTap={ onFocus }
>
-
- { __( 'Edit' ) }
+ { __( 'Detach' ) }
diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js
index 417ebed5b5e242..e4c28a22111c61 100644
--- a/packages/block-library/src/page-list/edit.js
+++ b/packages/block-library/src/page-list/edit.js
@@ -169,12 +169,6 @@ export default function PageListEdit( {
}, new Map() );
}, [ pages ] );
- const convertToNavigationLinks = useConvertToNavigationLinks( {
- clientId,
- pages,
- parentPageID,
- } );
-
const blockProps = useBlockProps( {
className: classnames( 'wp-block-page-list', {
'has-text-color': !! context.textColor,
@@ -189,68 +183,71 @@ export default function PageListEdit( {
style: { ...context.style?.color },
} );
- const getBlockList = ( parentId = parentPageID ) => {
- const childPages = pagesByParentId.get( parentId );
+ const pagesTree = useMemo(
+ function makePagesTree( parentId = 0, level = 0 ) {
+ const childPages = pagesByParentId.get( parentId );
- if ( ! childPages?.length ) {
- return [];
- }
-
- return childPages.reduce( ( template, page ) => {
- const hasChildren = pagesByParentId.has( page.id );
- const pageProps = {
- id: page.id,
- label:
- // translators: displayed when a page has an empty title.
- page.title?.rendered?.trim() !== ''
- ? page.title?.rendered
- : __( '(no title)' ),
- title: page.title?.rendered,
- link: page.url,
- hasChildren,
- };
- let item = null;
- const children = getBlockList( page.id );
- item = createBlock( 'core/page-list-item', pageProps, children );
- template.push( item );
-
- return template;
- }, [] );
- };
+ if ( ! childPages?.length ) {
+ return [];
+ }
- const makePagesTree = ( parentId = 0, level = 0 ) => {
- const childPages = pagesByParentId.get( parentId );
+ return childPages.reduce( ( tree, page ) => {
+ const hasChildren = pagesByParentId.has( page.id );
+ const item = {
+ value: page.id,
+ label: '— '.repeat( level ) + page.title.rendered,
+ rawName: page.title.rendered,
+ };
+ tree.push( item );
+ if ( hasChildren ) {
+ tree.push( ...makePagesTree( page.id, level + 1 ) );
+ }
+ return tree;
+ }, [] );
+ },
+ [ pagesByParentId ]
+ );
- if ( ! childPages?.length ) {
- return [];
- }
+ const blockList = useMemo(
+ function getBlockList( parentId = parentPageID ) {
+ const childPages = pagesByParentId.get( parentId );
- return childPages.reduce( ( tree, page ) => {
- const hasChildren = pagesByParentId.has( page.id );
- const item = {
- value: page.id,
- label: '— '.repeat( level ) + page.title.rendered,
- rawName: page.title.rendered,
- };
- tree.push( item );
- if ( hasChildren ) {
- tree.push( ...makePagesTree( page.id, level + 1 ) );
+ if ( ! childPages?.length ) {
+ return [];
}
- return tree;
- }, [] );
- };
- const pagesTree = useMemo( makePagesTree, [ pagesByParentId ] );
-
- const blockList = useMemo( getBlockList, [
- pagesByParentId,
- parentPageID,
- ] );
+ return childPages.reduce( ( template, page ) => {
+ const hasChildren = pagesByParentId.has( page.id );
+ const pageProps = {
+ id: page.id,
+ label:
+ // translators: displayed when a page has an empty title.
+ page.title?.rendered?.trim() !== ''
+ ? page.title?.rendered
+ : __( '(no title)' ),
+ title: page.title?.rendered,
+ link: page.url,
+ hasChildren,
+ };
+ let item = null;
+ const children = getBlockList( page.id );
+ item = createBlock(
+ 'core/page-list-item',
+ pageProps,
+ children
+ );
+ template.push( item );
+
+ return template;
+ }, [] );
+ },
+ [ pagesByParentId, parentPageID ]
+ );
const {
isNested,
hasSelectedChild,
- parentBlock,
+ parentClientId,
hasDraggedChild,
isChildOfNavigation,
} = useSelect(
@@ -258,7 +255,6 @@ export default function PageListEdit( {
const {
getBlockParentsByBlockName,
hasSelectedInnerBlock,
- getBlockRootClientId,
hasDraggedInnerBlock,
} = select( blockEditorStore );
const blockParents = getBlockParentsByBlockName(
@@ -276,12 +272,19 @@ export default function PageListEdit( {
isChildOfNavigation: navigationBlockParents.length > 0,
hasSelectedChild: hasSelectedInnerBlock( clientId, true ),
hasDraggedChild: hasDraggedInnerBlock( clientId, true ),
- parentBlock: getBlockRootClientId( clientId ),
+ parentClientId: navigationBlockParents[ 0 ],
};
},
[ clientId ]
);
+ const convertToNavigationLinks = useConvertToNavigationLinks( {
+ clientId,
+ pages,
+ parentClientId,
+ parentPageID,
+ } );
+
const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: [ 'core/page-list-item' ],
renderAppender: false,
@@ -297,12 +300,12 @@ export default function PageListEdit( {
useEffect( () => {
if ( hasSelectedChild || hasDraggedChild ) {
openModal();
- selectBlock( parentBlock );
+ selectBlock( parentClientId );
}
}, [
hasSelectedChild,
hasDraggedChild,
- parentBlock,
+ parentClientId,
selectBlock,
openModal,
] );
diff --git a/packages/block-library/src/page-list/use-convert-to-navigation-links.js b/packages/block-library/src/page-list/use-convert-to-navigation-links.js
index 5c62342fe77cd7..4cbc69d6e6de66 100644
--- a/packages/block-library/src/page-list/use-convert-to-navigation-links.js
+++ b/packages/block-library/src/page-list/use-convert-to-navigation-links.js
@@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { createBlock } from '@wordpress/blocks';
-import { useSelect, useDispatch } from '@wordpress/data';
+import { useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
/**
@@ -116,28 +116,11 @@ export function convertToNavigationLinks( pages = [], parentPageID = null ) {
export function useConvertToNavigationLinks( {
clientId,
pages,
+ parentClientId,
parentPageID,
} ) {
const { replaceBlock, selectBlock } = useDispatch( blockEditorStore );
- const { parentNavBlockClientId } = useSelect(
- ( select ) => {
- const { getSelectedBlockClientId, getBlockParentsByBlockName } =
- select( blockEditorStore );
-
- const _selectedBlockClientId = getSelectedBlockClientId();
-
- return {
- parentNavBlockClientId: getBlockParentsByBlockName(
- _selectedBlockClientId,
- 'core/navigation',
- true
- )[ 0 ],
- };
- },
- [ clientId ]
- );
-
return () => {
const navigationLinks = convertToNavigationLinks( pages, parentPageID );
@@ -145,6 +128,6 @@ export function useConvertToNavigationLinks( {
replaceBlock( clientId, navigationLinks );
// Select the Navigation block to reveal the changes.
- selectBlock( parentNavBlockClientId );
+ selectBlock( parentClientId );
};
}
diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json
index c5c4a71bf79252..85f56f4a838f50 100644
--- a/packages/block-library/src/paragraph/block.json
+++ b/packages/block-library/src/paragraph/block.json
@@ -1,12 +1,13 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/paragraph",
"title": "Paragraph",
"category": "text",
"description": "Start with the basic building block of all narrative.",
"keywords": [ "text" ],
"textdomain": "default",
+ "usesContext": [ "postId" ],
"attributes": {
"align": {
"type": "string"
@@ -41,6 +42,7 @@
"text": true
}
},
+ "__experimentalConnections": true,
"spacing": {
"margin": true,
"padding": true,
@@ -58,6 +60,7 @@
"__experimentalFontWeight": true,
"__experimentalLetterSpacing": true,
"__experimentalTextTransform": true,
+ "__experimentalWritingMode": true,
"__experimentalDefaultControls": {
"fontSize": true
}
diff --git a/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap
new file mode 100644
index 00000000000000..adc6ab4210efa5
--- /dev/null
+++ b/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Paragraph block should render without crashing and match snapshot 1`] = `
+
+
+
+`;
diff --git a/packages/block-library/src/paragraph/test/edit.native.js b/packages/block-library/src/paragraph/test/edit.native.js
index d3ff59c0e42c29..8220ad0888c795 100644
--- a/packages/block-library/src/paragraph/test/edit.native.js
+++ b/packages/block-library/src/paragraph/test/edit.native.js
@@ -13,6 +13,8 @@ import {
setupCoreBlocks,
waitFor,
within,
+ withFakeTimers,
+ waitForElementToBeRemoved,
} from 'test/helpers';
import Clipboard from '@react-native-clipboard/clipboard';
@@ -26,6 +28,19 @@ import { ENTER } from '@wordpress/keycodes';
*/
import Paragraph from '../edit';
+// Mock debounce to prevent potentially belated state updates.
+jest.mock( '@wordpress/compose/src/utils/debounce', () => ( {
+ debounce: ( fn ) => {
+ fn.cancel = jest.fn();
+ return fn;
+ },
+} ) );
+// Mock link suggestions that are fetched by the link picker
+// when typing a search query.
+jest.mock( '@wordpress/core-data/src/fetch', () => ( {
+ __experimentalFetchLinkSuggestions: jest.fn().mockResolvedValue( [ {} ] ),
+} ) );
+
setupCoreBlocks();
const getTestComponentWithContent = ( content ) => {
@@ -40,9 +55,9 @@ const getTestComponentWithContent = ( content ) => {
};
describe( 'Paragraph block', () => {
- it( 'renders without crashing', () => {
+ it( 'should render without crashing and match snapshot', () => {
const screen = getTestComponentWithContent( '' );
- expect( screen.container ).toBeTruthy();
+ expect( screen.toJSON() ).toMatchSnapshot();
} );
it( 'should bold text', async () => {
@@ -238,26 +253,27 @@ describe( 'Paragraph block', () => {
// Act
const paragraphBlock = getBlock( screen, 'Paragraph' );
fireEvent.press( paragraphBlock );
- // Await React Navigation: https://github.com/WordPress/gutenberg/issues/35685#issuecomment-961919931
- await act( () => fireEvent.press( screen.getByLabelText( 'Link' ) ) );
- // Await React Navigation: https://github.com/WordPress/gutenberg/issues/35685#issuecomment-961919931
- await act( () =>
- fireEvent.press(
- screen.getByLabelText( 'Link to, Search or type URL' )
- )
- );
- fireEvent.changeText(
- screen.getByPlaceholderText( 'Search or type URL' ),
- 'wordpress.org'
- );
+ fireEvent.press( screen.getByLabelText( 'Link' ) );
+
fireEvent.changeText(
screen.getByPlaceholderText( 'Add link text' ),
'WordPress'
);
- jest.useFakeTimers();
- fireEvent.press( screen.getByLabelText( 'Apply' ) );
- // Await link picker navigation delay
- act( () => jest.runOnlyPendingTimers() );
+ fireEvent.press(
+ screen.getByLabelText( 'Link to, Search or type URL' )
+ );
+ const typeURLInput = await waitFor( () =>
+ screen.getByPlaceholderText( 'Search or type URL' )
+ );
+ fireEvent.changeText( typeURLInput, 'wordpress.org' );
+ await waitForElementToBeRemoved( () =>
+ screen.getByTestId( 'link-picker-loading' )
+ );
+ // Back navigation from link picker uses `setTimeout`
+ await withFakeTimers( () => {
+ fireEvent.press( screen.getByLabelText( 'Apply' ) );
+ act( () => jest.runOnlyPendingTimers() );
+ } );
// Assert
expect( getEditorHtml() ).toMatchInlineSnapshot( `
@@ -265,8 +281,6 @@ describe( 'Paragraph block', () => {
WordPress
"
` );
-
- jest.useRealTimers();
} );
it( 'should link text with selection', async () => {
@@ -287,22 +301,22 @@ describe( 'Paragraph block', () => {
finalSelectionEnd: 7,
}
);
- // Await React Navigation: https://github.com/WordPress/gutenberg/issues/35685#issuecomment-961919931
- await act( () => fireEvent.press( screen.getByLabelText( 'Link' ) ) );
- // Await React Navigation: https://github.com/WordPress/gutenberg/issues/35685#issuecomment-961919931
- await act( () =>
- fireEvent.press(
- screen.getByLabelText( 'Link to, Search or type URL' )
- )
+ fireEvent.press( screen.getByLabelText( 'Link' ) );
+ fireEvent.press(
+ screen.getByLabelText( 'Link to, Search or type URL' )
);
- fireEvent.changeText(
- screen.getByPlaceholderText( 'Search or type URL' ),
- 'wordpress.org'
+ const typeURLInput = await waitFor( () =>
+ screen.getByPlaceholderText( 'Search or type URL' )
+ );
+ fireEvent.changeText( typeURLInput, 'wordpress.org' );
+ await waitForElementToBeRemoved( () =>
+ screen.getByTestId( 'link-picker-loading' )
);
- jest.useFakeTimers();
- fireEvent.press( screen.getByLabelText( 'Apply' ) );
- // Await link picker navigation delay
- act( () => jest.runOnlyPendingTimers() );
+ // Back navigation from link picker uses `setTimeout`
+ await withFakeTimers( () => {
+ fireEvent.press( screen.getByLabelText( 'Apply' ) );
+ act( () => jest.runOnlyPendingTimers() );
+ } );
// Assert
expect( getEditorHtml() ).toMatchInlineSnapshot( `
@@ -310,8 +324,6 @@ describe( 'Paragraph block', () => {
A quick brown fox jumps over the lazy dog.
"
` );
-
- jest.useRealTimers();
} );
it( 'should link text with clipboard contents', async () => {
@@ -402,6 +414,10 @@ describe( 'Paragraph block', () => {
// Tap one color
fireEvent.press( screen.getByLabelText( 'Pale pink' ) );
+ // TODO(jest-console): Fix the warning and remove the expect below.
+ expect( console ).toHaveWarnedWith(
+ `Non-serializable values were found in the navigation state. Check:\n\nColor > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.`
+ );
// Dismiss the Block Settings modal.
fireEvent( blockSettingsModal, 'backdropPress' );
@@ -639,4 +655,34 @@ describe( 'Paragraph block', () => {
);
expect( contrastCheckElement ).toBeDefined();
} );
+
+ it( 'should highlight text with selection', async () => {
+ // Arrange
+ const screen = await initializeEditor( { withGlobalStyles: true } );
+ await addBlock( screen, 'Paragraph' );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+ const paragraphTextInput =
+ within( paragraphBlock ).getByPlaceholderText( 'Start writing…' );
+ typeInRichText(
+ paragraphTextInput,
+ 'A quick brown fox jumps over the lazy dog.',
+ { finalSelectionStart: 2, finalSelectionEnd: 7 }
+ );
+ fireEvent.press( screen.getByLabelText( 'Text color' ) );
+ fireEvent.press( await screen.findByLabelText( 'Tertiary' ) );
+ // TODO(jest-console): Fix the warning and remove the expect below.
+ expect( console ).toHaveWarnedWith(
+ `Non-serializable values were found in the navigation state. Check:\n\ntext-color > Palette > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.`
+ );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchInlineSnapshot( `
+ "
+
A quick brown fox jumps over the lazy dog.
+ "
+ ` );
+ } );
} );
diff --git a/packages/block-library/src/paragraph/use-enter.js b/packages/block-library/src/paragraph/use-enter.js
index 22bef120ef17c9..04c0ba957af91a 100644
--- a/packages/block-library/src/paragraph/use-enter.js
+++ b/packages/block-library/src/paragraph/use-enter.js
@@ -6,7 +6,11 @@ import { useRefEffect } from '@wordpress/compose';
import { ENTER } from '@wordpress/keycodes';
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
-import { hasBlockSupport, createBlock } from '@wordpress/blocks';
+import {
+ hasBlockSupport,
+ createBlock,
+ getDefaultBlockName,
+} from '@wordpress/blocks';
export function useOnEnter( props ) {
const { batch } = useRegistry();
@@ -23,6 +27,7 @@ export function useOnEnter( props ) {
getBlockName,
getBlock,
getNextBlockClientId,
+ canInsertBlockType,
} = useSelect( blockEditorStore );
const propsRef = useRef( props );
propsRef.current = props;
@@ -56,22 +61,47 @@ export function useOnEnter( props ) {
}
const order = getBlockOrder( wrapperClientId );
-
- event.preventDefault();
-
const position = order.indexOf( clientId );
// If it is the last block, exit.
if ( position === order.length - 1 ) {
- moveBlocksToPosition(
- [ clientId ],
- wrapperClientId,
- getBlockRootClientId( wrapperClientId ),
- getBlockIndex( wrapperClientId ) + 1
- );
+ let newWrapperClientId = wrapperClientId;
+
+ while (
+ ! canInsertBlockType(
+ getBlockName( clientId ),
+ getBlockRootClientId( newWrapperClientId )
+ )
+ ) {
+ newWrapperClientId =
+ getBlockRootClientId( newWrapperClientId );
+ }
+
+ if ( typeof newWrapperClientId === 'string' ) {
+ event.preventDefault();
+ moveBlocksToPosition(
+ [ clientId ],
+ wrapperClientId,
+ getBlockRootClientId( newWrapperClientId ),
+ getBlockIndex( newWrapperClientId ) + 1
+ );
+ }
return;
}
+ const defaultBlockName = getDefaultBlockName();
+
+ if (
+ ! canInsertBlockType(
+ defaultBlockName,
+ getBlockRootClientId( wrapperClientId )
+ )
+ ) {
+ return;
+ }
+
+ event.preventDefault();
+
// If it is in the middle, split the block in two.
const wrapperBlock = getBlock( wrapperClientId );
batch( () => {
@@ -87,7 +117,7 @@ export function useOnEnter( props ) {
wrapperBlock.innerBlocks.slice( position + 1 )
);
insertBlock(
- createBlock( 'core/paragraph' ),
+ createBlock( defaultBlockName ),
blockIndex + 1,
getBlockRootClientId( wrapperClientId ),
true
diff --git a/packages/block-library/src/pattern/block.json b/packages/block-library/src/pattern/block.json
index 82372fe1680984..e9a85a9b2f84f1 100644
--- a/packages/block-library/src/pattern/block.json
+++ b/packages/block-library/src/pattern/block.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/pattern",
- "title": "Pattern",
+ "title": "Pattern placeholder",
"category": "theme",
"description": "Show a block pattern.",
"supports": {
@@ -13,10 +13,6 @@
"attributes": {
"slug": {
"type": "string"
- },
- "syncStatus": {
- "type": [ "string", "boolean" ],
- "enum": [ "full", "partial" ]
}
}
}
diff --git a/packages/block-library/src/pattern/edit.js b/packages/block-library/src/pattern/edit.js
index c22536a59eb03f..d9f0c2c53cebcd 100644
--- a/packages/block-library/src/pattern/edit.js
+++ b/packages/block-library/src/pattern/edit.js
@@ -7,77 +7,99 @@ import { useEffect } from '@wordpress/element';
import {
store as blockEditorStore,
useBlockProps,
- useInnerBlocksProps,
} from '@wordpress/block-editor';
+import { store as coreStore } from '@wordpress/core-data';
const PatternEdit = ( { attributes, clientId } ) => {
- const { slug, syncStatus } = attributes;
- const { selectedPattern, innerBlocks } = useSelect(
- ( select ) => {
- return {
- selectedPattern:
- select( blockEditorStore ).__experimentalGetParsedPattern(
- slug
- ),
- innerBlocks:
- select( blockEditorStore ).getBlock( clientId )
- ?.innerBlocks,
- };
- },
- [ slug, clientId ]
+ const selectedPattern = useSelect(
+ ( select ) =>
+ select( blockEditorStore ).__experimentalGetParsedPattern(
+ attributes.slug
+ ),
+ [ attributes.slug ]
);
- const {
- replaceBlocks,
- replaceInnerBlocks,
- __unstableMarkNextChangeAsNotPersistent,
- } = useDispatch( blockEditorStore );
+
+ const currentThemeStylesheet = useSelect(
+ ( select ) => select( coreStore ).getCurrentTheme().stylesheet
+ );
+
+ const { replaceBlocks, __unstableMarkNextChangeAsNotPersistent } =
+ useDispatch( blockEditorStore );
+ const { setBlockEditingMode } = useDispatch( blockEditorStore );
+ const { getBlockRootClientId, getBlockEditingMode } =
+ useSelect( blockEditorStore );
+
+ function injectThemeAttributeInBlockTemplateContent( block ) {
+ if (
+ block.innerBlocks.find(
+ ( innerBlock ) => innerBlock.name === 'core/template-part'
+ )
+ ) {
+ block.innerBlocks = block.innerBlocks.map( ( innerBlock ) => {
+ if (
+ innerBlock.name === 'core/template-part' &&
+ innerBlock.attributes.theme === undefined
+ ) {
+ innerBlock.attributes.theme = currentThemeStylesheet;
+ }
+ return innerBlock;
+ } );
+ }
+
+ if (
+ block.name === 'core/template-part' &&
+ block.attributes.theme === undefined
+ ) {
+ block.attributes.theme = currentThemeStylesheet;
+ }
+ return block;
+ }
// Run this effect when the component loads.
// This adds the Pattern's contents to the post.
+ // This change won't be saved.
+ // It will continue to pull from the pattern file unless changes are made to its respective template part.
useEffect( () => {
- if ( selectedPattern?.blocks && ! innerBlocks?.length ) {
+ if ( selectedPattern?.blocks ) {
// We batch updates to block list settings to avoid triggering cascading renders
// for each container block included in a tree and optimize initial render.
// Since the above uses microtasks, we need to use a microtask here as well,
// because nested pattern blocks cannot be inserted if the parent block supports
// inner blocks but doesn't have blockSettings in the state.
window.queueMicrotask( () => {
+ const rootClientId = getBlockRootClientId( clientId );
// Clone blocks from the pattern before insertion to ensure they receive
// distinct client ids. See https://github.com/WordPress/gutenberg/issues/50628.
const clonedBlocks = selectedPattern.blocks.map( ( block ) =>
- cloneBlock( block )
+ cloneBlock(
+ injectThemeAttributeInBlockTemplateContent( block )
+ )
);
+ const rootEditingMode = getBlockEditingMode( rootClientId );
+ // Temporarily set the root block to default mode to allow replacing the pattern.
+ // This could happen when the page is disabling edits of non-content blocks.
+ __unstableMarkNextChangeAsNotPersistent();
+ setBlockEditingMode( rootClientId, 'default' );
__unstableMarkNextChangeAsNotPersistent();
- if ( syncStatus === 'partial' ) {
- replaceInnerBlocks( clientId, clonedBlocks );
- return;
- }
replaceBlocks( clientId, clonedBlocks );
+ // Restore the root block's original mode.
+ __unstableMarkNextChangeAsNotPersistent();
+ setBlockEditingMode( rootClientId, rootEditingMode );
} );
}
}, [
clientId,
selectedPattern?.blocks,
- replaceInnerBlocks,
__unstableMarkNextChangeAsNotPersistent,
- innerBlocks,
- syncStatus,
replaceBlocks,
+ getBlockEditingMode,
+ setBlockEditingMode,
+ getBlockRootClientId,
] );
- const blockProps = useBlockProps( {
- className: slug?.replace( '/', '-' ),
- } );
-
- const innerBlocksProps = useInnerBlocksProps( blockProps, {
- templateLock: syncStatus === 'partial' ? 'contentOnly' : false,
- } );
-
- if ( syncStatus !== 'partial' ) {
- return
;
- }
+ const props = useBlockProps();
- return
;
+ return
;
};
export default PatternEdit;
diff --git a/packages/block-library/src/pattern/index.js b/packages/block-library/src/pattern/index.js
index 27e74510eb5972..e4af712da8bb29 100644
--- a/packages/block-library/src/pattern/index.js
+++ b/packages/block-library/src/pattern/index.js
@@ -3,14 +3,13 @@
*/
import initBlock from '../utils/init-block';
import metadata from './block.json';
-import PatternEditV1 from './v1/edit';
-import PatternEditV2 from './edit';
+import PatternEdit from './edit';
const { name } = metadata;
export { metadata, name };
-export const settings = window?.__experimentalEnablePatternEnhancements
- ? { edit: PatternEditV2 }
- : { edit: PatternEditV1 };
+export const settings = {
+ edit: PatternEdit,
+};
export const init = () => initBlock( { name, metadata, settings } );
diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php
index cb3be0370a4f6f..97a8c3ddc663f8 100644
--- a/packages/block-library/src/pattern/index.php
+++ b/packages/block-library/src/pattern/index.php
@@ -22,6 +22,8 @@ function register_block_core_pattern() {
/**
* Renders the `core/pattern` block on the server.
*
+ * @since 6.3.0 Backwards compatibility: blocks with no `syncStatus` attribute do not receive block wrapper.
+ *
* @param array $attributes Block attributes.
*
* @return string Returns the output of the pattern.
@@ -39,19 +41,17 @@ function render_block_core_pattern( $attributes ) {
}
$pattern = $registry->get_registered( $slug );
-
- // Currently all existing blocks should be returned here without a wp-block-pattern wrapper
- // as the syncStatus attribute is only used if the gutenberg-pattern-enhancements experiment
- // is enabled.
- if ( ! isset( $attributes['syncStatus'] ) ) {
- return do_blocks( $pattern['content'] );
+ $content = _inject_theme_attribute_in_block_template_content( $pattern['content'] );
+
+ $gutenberg_experiments = get_option( 'gutenberg-experiments' );
+ if ( $gutenberg_experiments && ! empty( $gutenberg_experiments['gutenberg-auto-inserting-blocks'] ) ) {
+ // TODO: In the long run, we'd likely want to have a filter in the `WP_Block_Patterns_Registry` class
+ // instead to allow us plugging in code like this.
+ $blocks = parse_blocks( $content );
+ $content = gutenberg_serialize_blocks( $blocks );
}
- $block_classnames = 'wp-block-pattern ' . str_replace( '/', '-', $attributes['slug'] );
- $classnames = isset( $attributes['className'] ) ? $attributes['className'] . ' ' . $block_classnames : $block_classnames;
- $wrapper = '
%s
';
-
- return sprintf( $wrapper, do_blocks( $pattern['content'] ) );
+ return do_blocks( $content );
}
add_action( 'init', 'register_block_core_pattern' );
diff --git a/packages/block-library/src/pattern/v1/edit.js b/packages/block-library/src/pattern/v1/edit.js
deleted file mode 100644
index b4900536ec274f..00000000000000
--- a/packages/block-library/src/pattern/v1/edit.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { cloneBlock } from '@wordpress/blocks';
-import { useSelect, useDispatch } from '@wordpress/data';
-import { useEffect } from '@wordpress/element';
-import {
- store as blockEditorStore,
- useBlockProps,
-} from '@wordpress/block-editor';
-
-const PatternEdit = ( { attributes, clientId } ) => {
- const selectedPattern = useSelect(
- ( select ) =>
- select( blockEditorStore ).__experimentalGetParsedPattern(
- attributes.slug
- ),
- [ attributes.slug ]
- );
-
- const { replaceBlocks, __unstableMarkNextChangeAsNotPersistent } =
- useDispatch( blockEditorStore );
-
- // Run this effect when the component loads.
- // This adds the Pattern's contents to the post.
- // This change won't be saved.
- // It will continue to pull from the pattern file unless changes are made to its respective template part.
- useEffect( () => {
- if ( selectedPattern?.blocks ) {
- // We batch updates to block list settings to avoid triggering cascading renders
- // for each container block included in a tree and optimize initial render.
- // Since the above uses microtasks, we need to use a microtask here as well,
- // because nested pattern blocks cannot be inserted if the parent block supports
- // inner blocks but doesn't have blockSettings in the state.
- window.queueMicrotask( () => {
- // Clone blocks from the pattern before insertion to ensure they receive
- // distinct client ids. See https://github.com/WordPress/gutenberg/issues/50628.
- const clonedBlocks = selectedPattern.blocks.map( ( block ) =>
- cloneBlock( block )
- );
- __unstableMarkNextChangeAsNotPersistent();
- replaceBlocks( clientId, clonedBlocks );
- } );
- }
- }, [
- clientId,
- selectedPattern?.blocks,
- __unstableMarkNextChangeAsNotPersistent,
- replaceBlocks,
- ] );
-
- const props = useBlockProps();
-
- return
;
-};
-
-export default PatternEdit;
diff --git a/packages/block-library/src/post-author-biography/block.json b/packages/block-library/src/post-author-biography/block.json
index a2e5f327acfeb7..5d7a4d4585747d 100644
--- a/packages/block-library/src/post-author-biography/block.json
+++ b/packages/block-library/src/post-author-biography/block.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-author-biography",
- "title": "Post Author Biography",
+ "title": "Author Biography",
"category": "theme",
"description": "The author biography.",
"textdomain": "default",
@@ -13,7 +13,6 @@
},
"usesContext": [ "postType", "postId" ],
"supports": {
- "anchor": true,
"spacing": {
"margin": true,
"padding": true
diff --git a/packages/block-library/src/post-author-name/block.json b/packages/block-library/src/post-author-name/block.json
index 2340636e0c63a4..89e4b38de2c281 100644
--- a/packages/block-library/src/post-author-name/block.json
+++ b/packages/block-library/src/post-author-name/block.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-author-name",
- "title": "Post Author Name",
+ "title": "Author Name",
"category": "theme",
"description": "The author name.",
"textdomain": "default",
@@ -21,7 +21,6 @@
},
"usesContext": [ "postType", "postId" ],
"supports": {
- "anchor": true,
"html": false,
"spacing": {
"margin": true,
diff --git a/packages/block-library/src/post-author/block.json b/packages/block-library/src/post-author/block.json
index 4a8e0433868e53..47dceef55604f6 100644
--- a/packages/block-library/src/post-author/block.json
+++ b/packages/block-library/src/post-author/block.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-author",
- "title": "Post Author",
+ "title": "Author",
"category": "theme",
"description": "Display post author details such as name, avatar, and bio.",
"textdomain": "default",
@@ -35,7 +35,6 @@
},
"usesContext": [ "postType", "postId", "queryId" ],
"supports": {
- "anchor": true,
"html": false,
"spacing": {
"margin": true,
diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js
index a9b7106f49d388..4ee353fdd9bdc0 100644
--- a/packages/block-library/src/post-author/edit.js
+++ b/packages/block-library/src/post-author/edit.js
@@ -93,14 +93,14 @@ function PostAuthorEdit( {
};
const showCombobox = authorOptions.length >= minimumUsersForCombobox;
+ const showAuthorControl =
+ !! postId && ! isDescendentOfQueryLoop && authorOptions.length > 0;
return (
<>
- { !! postId &&
- ! isDescendentOfQueryLoop &&
- authorOptions.length &&
+ { showAuthorControl &&
( ( showCombobox && (
{
+ return select( coreStore ).getEntityRecord(
+ 'postType',
+ postType,
+ postId
+ );
+ },
+ [ postType, postId ]
+ );
+
+ const hasInnerBlocks = !! entityRecord?.content?.raw || blocks?.length;
+
+ const initialInnerBlocks = [ [ 'core/paragraph' ] ];
+
const props = useInnerBlocksProps(
useBlockProps( { className: 'entry-content' } ),
{
value: blocks,
onInput,
onChange,
+ template: ! hasInnerBlocks ? initialInnerBlocks : undefined,
}
);
return ;
@@ -83,7 +103,7 @@ function Placeholder( { layoutClassNames } ) {
{ __(
- 'This is the Post Content block, it will display all the blocks in any single post or page.'
+ 'This is the Content block, it will display all the blocks in any single post or page.'
) }
@@ -93,7 +113,7 @@ function Placeholder( { layoutClassNames } ) {
{ __(
- 'If there are any Custom Post Types registered at your site, the Post Content block can display the contents of those entries as well.'
+ 'If there are any Custom Post Types registered at your site, the Content block can display the contents of those entries as well.'
) }
diff --git a/packages/block-library/src/post-content/index.php b/packages/block-library/src/post-content/index.php
index 2be1ef77b3b856..dd84574fdea658 100644
--- a/packages/block-library/src/post-content/index.php
+++ b/packages/block-library/src/post-content/index.php
@@ -35,12 +35,6 @@ function render_block_core_post_content( $attributes, $content, $block ) {
$seen_ids[ $post_id ] = true;
- // Check is needed for backward compatibility with third-party plugins
- // that might rely on the `in_the_loop` check; calling `the_post` sets it to true.
- if ( ! in_the_loop() && have_posts() ) {
- the_post();
- }
-
// When inside the main loop, we want to use queried object
// so that `the_preview` for the current post can apply.
// We force this behavior by omitting the third argument (post ID) from the `get_the_content`.
diff --git a/packages/block-library/src/post-date/block.json b/packages/block-library/src/post-date/block.json
index 41c45a4a57e26e..11ebc32d9cabec 100644
--- a/packages/block-library/src/post-date/block.json
+++ b/packages/block-library/src/post-date/block.json
@@ -1,10 +1,10 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-date",
- "title": "Post Date",
+ "title": "Date",
"category": "theme",
- "description": "Add the date of this post.",
+ "description": "Display the publish date for an entry such as a post or page.",
"textdomain": "default",
"attributes": {
"textAlign": {
@@ -24,7 +24,6 @@
},
"usesContext": [ "postId", "postType", "queryId" ],
"supports": {
- "anchor": true,
"html": false,
"color": {
"gradients": true,
diff --git a/packages/block-library/src/post-date/variations.js b/packages/block-library/src/post-date/variations.js
index 0b99b9d5136883..caeed79f93eab4 100644
--- a/packages/block-library/src/post-date/variations.js
+++ b/packages/block-library/src/post-date/variations.js
@@ -7,7 +7,7 @@ import { postDate } from '@wordpress/icons';
const variations = [
{
name: 'post-date-modified',
- title: __( 'Post Modified Date' ),
+ title: __( 'Modified Date' ),
description: __( "Display a post's last updated date." ),
attributes: { displayType: 'modified' },
scope: [ 'block', 'inserter' ],
diff --git a/packages/block-library/src/post-excerpt/block.json b/packages/block-library/src/post-excerpt/block.json
index 53a92cb0bda639..33b7818ebed9f2 100644
--- a/packages/block-library/src/post-excerpt/block.json
+++ b/packages/block-library/src/post-excerpt/block.json
@@ -1,10 +1,10 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-excerpt",
- "title": "Post Excerpt",
+ "title": "Excerpt",
"category": "theme",
- "description": "Display a post's excerpt.",
+ "description": "Display the excerpt.",
"textdomain": "default",
"attributes": {
"textAlign": {
@@ -24,7 +24,6 @@
},
"usesContext": [ "postId", "postType", "queryId" ],
"supports": {
- "anchor": true,
"html": false,
"color": {
"gradients": true,
diff --git a/packages/block-library/src/post-excerpt/edit.js b/packages/block-library/src/post-excerpt/edit.js
index b84abaa4e98d83..c4b53b22833698 100644
--- a/packages/block-library/src/post-excerpt/edit.js
+++ b/packages/block-library/src/post-excerpt/edit.js
@@ -109,16 +109,7 @@ export default function PostExcerptEditor( {
/>
-
- { __(
- 'This is the Post Excerpt block, it will display the excerpt from single posts.'
- ) }
-
-
- { __(
- 'If there are any Custom Post Types with support for excerpts, the Post Excerpt block can display the excerpts of those entries as well.'
- ) }
-
+
{ __( 'This block will display the excerpt.' ) }
>
);
@@ -128,7 +119,7 @@ export default function PostExcerptEditor( {
{ __(
- 'There is no excerpt because this is a protected post.'
+ 'The content is currently protected and does not have the available excerpt.'
) }
@@ -195,14 +186,14 @@ export default function PostExcerptEditor( {
const excerptContent = isEditable ? (
{ ! isTrimmed
- ? rawOrRenderedExcerpt || __( 'No post excerpt found' )
+ ? rawOrRenderedExcerpt || __( 'No excerpt found' )
: trimmedExcerpt + ELLIPSIS }
);
diff --git a/packages/block-library/src/post-excerpt/index.php b/packages/block-library/src/post-excerpt/index.php
index 24f6777b4121de..4ed4edab95078b 100644
--- a/packages/block-library/src/post-excerpt/index.php
+++ b/packages/block-library/src/post-excerpt/index.php
@@ -31,7 +31,7 @@ function render_block_core_post_excerpt( $attributes, $content, $block ) {
}
$more_text = ! empty( $attributes['moreText'] ) ? '' . wp_kses_post( $attributes['moreText'] ) . '' : '';
- $filter_excerpt_more = function( $more ) use ( $more_text ) {
+ $filter_excerpt_more = static function( $more ) use ( $more_text ) {
return empty( $more_text ) ? $more : '';
};
/**
@@ -87,7 +87,7 @@ function register_block_core_post_excerpt() {
defined( 'REST_REQUEST' ) && REST_REQUEST ) {
add_filter(
'excerpt_length',
- function() {
+ static function() {
return 100;
},
PHP_INT_MAX
diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json
index c6007785cd82ac..34e3bd6b2325fa 100644
--- a/packages/block-library/src/post-featured-image/block.json
+++ b/packages/block-library/src/post-featured-image/block.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-featured-image",
- "title": "Post Featured Image",
+ "title": "Featured Image",
"category": "theme",
"description": "Display a post's featured image.",
"textdomain": "default",
@@ -56,7 +56,6 @@
"usesContext": [ "postId", "postType", "queryId" ],
"supports": {
"align": [ "left", "right", "center", "wide", "full" ],
- "anchor": true,
"color": {
"__experimentalDuotone": "img, .wp-block-post-featured-image__placeholder, .components-placeholder__illustration, .components-placeholder::before",
"text": false,
diff --git a/packages/block-library/src/post-featured-image/index.php b/packages/block-library/src/post-featured-image/index.php
index 6cb4110ee000e6..67c889b0befa51 100644
--- a/packages/block-library/src/post-featured-image/index.php
+++ b/packages/block-library/src/post-featured-image/index.php
@@ -19,12 +19,6 @@ function render_block_core_post_featured_image( $attributes, $content, $block )
}
$post_ID = $block->context['postId'];
- // Check is needed for backward compatibility with third-party plugins
- // that might rely on the `in_the_loop` check; calling `the_post` sets it to true.
- if ( ! in_the_loop() && have_posts() ) {
- the_post();
- }
-
$is_link = isset( $attributes['isLink'] ) && $attributes['isLink'];
$size_slug = isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'post-thumbnail';
$attr = get_block_core_post_featured_image_border_attributes( $attributes );
diff --git a/packages/block-library/src/post-navigation-link/block.json b/packages/block-library/src/post-navigation-link/block.json
index 2bdfa654798ee6..e1b6d4fa90a40c 100644
--- a/packages/block-library/src/post-navigation-link/block.json
+++ b/packages/block-library/src/post-navigation-link/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-navigation-link",
"title": "Post Navigation Link",
"category": "theme",
@@ -31,7 +31,6 @@
}
},
"supports": {
- "anchor": true,
"reusable": false,
"html": false,
"color": {
@@ -46,6 +45,7 @@
"__experimentalTextTransform": true,
"__experimentalTextDecoration": true,
"__experimentalLetterSpacing": true,
+ "__experimentalWritingMode": true,
"__experimentalDefaultControls": {
"fontSize": true
}
diff --git a/packages/block-library/src/post-navigation-link/index.php b/packages/block-library/src/post-navigation-link/index.php
index cb1fe568bdf0e7..cb066ad69f2c5d 100644
--- a/packages/block-library/src/post-navigation-link/index.php
+++ b/packages/block-library/src/post-navigation-link/index.php
@@ -88,9 +88,9 @@ function render_block_core_post_navigation_link( $attributes, $content ) {
$arrow = $arrow_map[ $attributes['arrow'] ][ $navigation_type ];
if ( 'next' === $navigation_type ) {
- $format = '%link ' . $arrow . '';
+ $format = '%link' . $arrow . '';
} else {
- $format = '' . $arrow . ' %link';
+ $format = '' . $arrow . '%link';
}
}
diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json
index 6c2056368d6449..48804de75d2cae 100644
--- a/packages/block-library/src/post-template/block.json
+++ b/packages/block-library/src/post-template/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-template",
"title": "Post Template",
"category": "theme",
@@ -13,16 +13,14 @@
"queryContext",
"displayLayout",
"templateSlug",
- "previewPostType"
+ "previewPostType",
+ "enhancedPagination"
],
"supports": {
"reusable": false,
"html": false,
"align": [ "wide", "full" ],
- "anchor": true,
- "__experimentalLayout": {
- "allowEditing": false
- },
+ "layout": true,
"color": {
"gradients": true,
"link": true,
@@ -43,6 +41,14 @@
"__experimentalDefaultControls": {
"fontSize": true
}
+ },
+ "spacing": {
+ "blockGap": {
+ "__experimentalDefault": "1.25em"
+ },
+ "__experimentalDefaultControls": {
+ "blockGap": true
+ }
}
},
"style": "wp-block-post-template",
diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js
index 1acb3e57191758..f05f81c14082ef 100644
--- a/packages/block-library/src/post-template/edit.js
+++ b/packages/block-library/src/post-template/edit.js
@@ -10,14 +10,16 @@ import { memo, useMemo, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import {
+ BlockControls,
BlockContextProvider,
__experimentalUseBlockPreview as useBlockPreview,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
-import { Spinner } from '@wordpress/components';
+import { Spinner, ToolbarGroup } from '@wordpress/components';
import { store as coreStore } from '@wordpress/core-data';
+import { list, grid } from '@wordpress/icons';
const TEMPLATE = [
[ 'core/post-title' ],
@@ -70,6 +72,7 @@ function PostTemplateBlockPreview( {
const MemoizedPostTemplateBlockPreview = memo( PostTemplateBlockPreview );
export default function PostTemplateEdit( {
+ setAttributes,
clientId,
context: {
query: {
@@ -95,11 +98,13 @@ export default function PostTemplateEdit( {
} = {},
queryContext = [ { page: 1 } ],
templateSlug,
- displayLayout: { type: layoutType = 'flex', columns = 1 } = {},
previewPostType,
},
+ attributes: { layout },
__unstableLayoutClassNames,
} ) {
+ const { type: layoutType, columnCount = 3 } = layout || {};
+
const [ { page } ] = queryContext;
const [ activeBlockContextId, setActiveBlockContextId ] = useState();
const { posts, blocks } = useSelect(
@@ -215,11 +220,11 @@ export default function PostTemplateEdit( {
} ) ),
[ posts ]
);
- const hasLayoutFlex = layoutType === 'flex' && columns > 1;
+
const blockProps = useBlockProps( {
className: classnames( __unstableLayoutClassNames, {
- 'is-flex-container': hasLayoutFlex,
- [ `columns-${ columns }` ]: hasLayoutFlex,
+ [ `columns-${ columnCount }` ]:
+ layoutType === 'grid' && columnCount, // Ensure column count is flagged via classname for backwards compatibility.
} ),
} );
@@ -235,35 +240,67 @@ export default function PostTemplateEdit( {
return { __( 'No results found.' ) }
;
}
+ const setDisplayLayout = ( newDisplayLayout ) =>
+ setAttributes( {
+ layout: { ...layout, ...newDisplayLayout },
+ } );
+
+ const displayLayoutControls = [
+ {
+ icon: list,
+ title: __( 'List view' ),
+ onClick: () => setDisplayLayout( { type: 'default' } ),
+ isActive: layoutType === 'default' || layoutType === 'constrained',
+ },
+ {
+ icon: grid,
+ title: __( 'Grid view' ),
+ onClick: () =>
+ setDisplayLayout( {
+ type: 'grid',
+ columnCount,
+ } ),
+ isActive: layoutType === 'grid',
+ },
+ ];
+
// To avoid flicker when switching active block contexts, a preview is rendered
// for each block context, but the preview for the active block context is hidden.
// This ensures that when it is displayed again, the cached rendering of the
// block preview is used, instead of having to re-render the preview from scratch.
return (
-
- { blockContexts &&
- blockContexts.map( ( blockContext ) => (
-
- { blockContext.postId ===
- ( activeBlockContextId ||
- blockContexts[ 0 ]?.postId ) ? (
-
- ) : null }
-
-
- ) ) }
-
+ <>
+
+
+
+
+
+ { blockContexts &&
+ blockContexts.map( ( blockContext ) => (
+
+ { blockContext.postId ===
+ ( activeBlockContextId ||
+ blockContexts[ 0 ]?.postId ) ? (
+
+ ) : null }
+
+
+ ) ) }
+
+ >
);
}
diff --git a/packages/block-library/src/post-template/index.php b/packages/block-library/src/post-template/index.php
index 3a3c207cf92ee2..e616939514a682 100644
--- a/packages/block-library/src/post-template/index.php
+++ b/packages/block-library/src/post-template/index.php
@@ -34,6 +34,8 @@ function block_core_post_template_uses_featured_image( $inner_blocks ) {
/**
* Renders the `core/post-template` block on the server.
*
+ * @since 6.3.0 Changed render_block_context priority to `1`.
+ *
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
@@ -41,14 +43,26 @@ function block_core_post_template_uses_featured_image( $inner_blocks ) {
* @return string Returns the output of the query, structured using the layout defined by the block's inner blocks.
*/
function render_block_core_post_template( $attributes, $content, $block ) {
- $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
- $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
+ $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
+ $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination'];
+ $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
// Use global query if needed.
$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
if ( $use_global_query ) {
global $wp_query;
- $query = clone $wp_query;
+
+ /*
+ * If already in the main query loop, duplicate the query instance to not tamper with the main instance.
+ * Since this is a nested query, it should start at the beginning, therefore rewind posts.
+ * Otherwise, the main query loop has not started yet and this block is responsible for doing so.
+ */
+ if ( in_the_loop() ) {
+ $query = clone $wp_query;
+ $query->rewind_posts();
+ } else {
+ $query = $wp_query;
+ }
} else {
$query_args = build_query_vars_from_query_block( $block, $page );
$query = new WP_Query( $query_args );
@@ -72,6 +86,11 @@ function render_block_core_post_template( $attributes, $content, $block ) {
$classnames .= ' has-link-color';
}
+ // Ensure backwards compatibility by flagging the number of columns via classname when using grid layout.
+ if ( isset( $attributes['layout']['type'] ) && 'grid' === $attributes['layout']['type'] && ! empty( $attributes['layout']['columnCount'] ) ) {
+ $classnames .= ' ' . sanitize_title( 'columns-' . $attributes['layout']['columnCount'] );
+ }
+
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classnames ) ) );
$content = '';
@@ -85,21 +104,27 @@ function render_block_core_post_template( $attributes, $content, $block ) {
// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
$block_instance['blockName'] = 'core/null';
+ $post_id = get_the_ID();
+ $post_type = get_post_type();
+ $filter_block_context = static function( $context ) use ( $post_id, $post_type ) {
+ $context['postType'] = $post_type;
+ $context['postId'] = $post_id;
+ return $context;
+ };
+
+ // Use an early priority to so that other 'render_block_context' filters have access to the values.
+ add_filter( 'render_block_context', $filter_block_context, 1 );
// Render the inner blocks of the Post Template block with `dynamic` set to `false` to prevent calling
// `render_callback` and ensure that no wrapper markup is included.
- $block_content = (
- new WP_Block(
- $block_instance,
- array(
- 'postType' => get_post_type(),
- 'postId' => get_the_ID(),
- )
- )
- )->render( array( 'dynamic' => false ) );
+ $block_content = ( new WP_Block( $block_instance ) )->render( array( 'dynamic' => false ) );
+ remove_filter( 'render_block_context', $filter_block_context, 1 );
// Wrap the render inner blocks in a `li` element with the appropriate post classes.
$post_classes = implode( ' ', get_post_class( 'wp-block-post' ) );
- $content .= '' . $block_content . '';
+
+ $inner_block_directives = $enhanced_pagination ? ' data-wp-key="post-template-item-' . $post_id . '"' : '';
+
+ $content .= '' . $block_content . '';
}
/*
diff --git a/packages/block-library/src/post-template/style.scss b/packages/block-library/src/post-template/style.scss
index b1cdcf385e223c..00305a17123369 100644
--- a/packages/block-library/src/post-template/style.scss
+++ b/packages/block-library/src/post-template/style.scss
@@ -9,7 +9,7 @@
&.wp-block-post-template {
background: none;
}
-
+ // These rules no longer apply but should be kept for backwards compatibility.
&.is-flex-container {
flex-direction: row;
display: flex;
@@ -30,3 +30,10 @@
}
}
}
+
+@media ( max-width: $break-small ) {
+ // Temporary specificity bump until "wp-container" layout specificity is revisited.
+ .wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/packages/block-library/src/post-terms/block.json b/packages/block-library/src/post-terms/block.json
index 1633c7c01b82ca..0da7fb02f8134f 100644
--- a/packages/block-library/src/post-terms/block.json
+++ b/packages/block-library/src/post-terms/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-terms",
"title": "Post Terms",
"category": "theme",
@@ -28,7 +28,6 @@
},
"usesContext": [ "postId", "postType" ],
"supports": {
- "anchor": true,
"html": false,
"color": {
"gradients": true,
diff --git a/packages/block-library/src/post-terms/edit.js b/packages/block-library/src/post-terms/edit.js
index d46c738e79ec4e..49e3a4ce801f42 100644
--- a/packages/block-library/src/post-terms/edit.js
+++ b/packages/block-library/src/post-terms/edit.js
@@ -69,10 +69,6 @@ export default function PostTermsEdit( {
} ),
} );
- if ( ! hasPost || ! term ) {
- return { blockInformation.title }
;
- }
-
return (
<>
@@ -96,7 +92,7 @@ export default function PostTermsEdit( {
/>
- { isLoading &&
}
+ { isLoading && hasPost &&
}
{ ! isLoading && hasPostTerms && ( isSelected || prefix ) && (
) }
- { ! isLoading &&
+ { ( ! hasPost || ! term ) && (
+
{ blockInformation.title }
+ ) }
+ { hasPost &&
+ ! isLoading &&
hasPostTerms &&
postTerms
.map( ( postTerm ) => (
@@ -132,7 +132,8 @@ export default function PostTermsEdit( {
{ curr }
>
) ) }
- { ! isLoading &&
+ { hasPost &&
+ ! isLoading &&
! hasPostTerms &&
( selectedTerm?.labels?.no_terms ||
__( 'Term items not found.' ) ) }
diff --git a/packages/block-library/src/post-terms/hooks.js b/packages/block-library/src/post-terms/hooks.js
index 539ea837a30b8d..2851cd145e9a33 100644
--- a/packages/block-library/src/post-terms/hooks.js
+++ b/packages/block-library/src/post-terms/hooks.js
@@ -16,9 +16,9 @@ export default function enhanceVariations( settings, name ) {
}
const variations = settings.variations.map( ( variation ) => ( {
...variation,
- ...( variationIconMap[ variation.name ] && {
- icon: variationIconMap[ variation.name ],
- } ),
+ ...{
+ icon: variationIconMap[ variation.name ] ?? postCategories,
+ },
} ) );
return {
...settings,
diff --git a/packages/block-library/src/post-time-to-read/block.json b/packages/block-library/src/post-time-to-read/block.json
index 2b7d7936094f7c..281e9bb1f1b210 100644
--- a/packages/block-library/src/post-time-to-read/block.json
+++ b/packages/block-library/src/post-time-to-read/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"__experimental": true,
"name": "core/post-time-to-read",
"title": "Time To Read",
@@ -24,7 +24,11 @@
"html": false,
"spacing": {
"margin": true,
- "padding": true
+ "padding": true,
+ "__experimentalDefaultControls": {
+ "margin": false,
+ "padding": false
+ }
},
"typography": {
"fontSize": true,
diff --git a/packages/block-library/src/post-time-to-read/init.js b/packages/block-library/src/post-time-to-read/init.js
new file mode 100644
index 00000000000000..79f0492c2cb2f8
--- /dev/null
+++ b/packages/block-library/src/post-time-to-read/init.js
@@ -0,0 +1,6 @@
+/**
+ * Internal dependencies
+ */
+import { init } from './';
+
+export default init();
diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json
index 4a56a6f37b7795..eda5332f240223 100644
--- a/packages/block-library/src/post-title/block.json
+++ b/packages/block-library/src/post-title/block.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/post-title",
- "title": "Post Title",
+ "title": "Title",
"category": "theme",
"description": "Displays the title of a post, page, or any other content-type.",
"textdomain": "default",
@@ -31,7 +31,6 @@
},
"supports": {
"align": [ "wide", "full" ],
- "anchor": true,
"html": false,
"color": {
"gradients": true,
diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js
index 8cd71881e06dec..28d9b8cdcfd8ff 100644
--- a/packages/block-library/src/post-title/edit.js
+++ b/packages/block-library/src/post-title/edit.js
@@ -12,6 +12,8 @@ import {
InspectorControls,
useBlockProps,
PlainText,
+ HeadingLevelDropdown,
+ useBlockEditingMode,
} from '@wordpress/block-editor';
import { ToggleControl, TextControl, PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
@@ -21,7 +23,6 @@ import { useEntityProp } from '@wordpress/core-data';
/**
* Internal dependencies
*/
-import HeadingLevelDropdown from '../heading/heading-level-dropdown';
import { useCanEditEntity } from '../utils/hooks';
export default function PostTitleEdit( {
@@ -30,7 +31,7 @@ export default function PostTitleEdit( {
context: { postType, postId, queryId },
insertBlocksAfter,
} ) {
- const TagName = 0 === level ? 'p' : 'h' + level;
+ const TagName = 'h' + level;
const isDescendentOfQueryLoop = Number.isFinite( queryId );
/**
* Hack: useCanEditEntity may trigger an OPTIONS request to the REST API via the canUser resolver.
@@ -58,10 +59,9 @@ export default function PostTitleEdit( {
[ `has-text-align-${ textAlign }` ]: textAlign,
} ),
} );
+ const blockEditingMode = useBlockEditingMode();
- let titleElement = (
-
{ __( 'Post Title' ) }
- );
+ let titleElement =
{ __( 'Title' ) };
if ( postType && postId ) {
titleElement = userCanEdit ? (
@@ -114,20 +114,22 @@ export default function PostTitleEdit( {
return (
<>
-
-
- setAttributes( { level: newLevel } )
- }
- />
- {
- setAttributes( { textAlign: nextAlign } );
- } }
- />
-
+ { blockEditingMode === 'default' && (
+
+
+ setAttributes( { level: newLevel } )
+ }
+ />
+ {
+ setAttributes( { textAlign: nextAlign } );
+ } }
+ />
+
+ ) }
context['postId'] );
- $title = get_the_title( $post );
+ /**
+ * The `$post` argument is intentionally omitted so that changes are reflected when previewing a post.
+ * See: https://github.com/WordPress/gutenberg/pull/37622#issuecomment-1000932816.
+ */
+ $title = get_the_title();
if ( ! $title ) {
return '';
@@ -28,12 +33,12 @@ function render_block_core_post_title( $attributes, $content, $block ) {
$tag_name = 'h2';
if ( isset( $attributes['level'] ) ) {
- $tag_name = 0 === $attributes['level'] ? 'p' : 'h' . $attributes['level'];
+ $tag_name = 'h' . $attributes['level'];
}
if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) {
$rel = ! empty( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : '';
- $title = sprintf( '%4$s', get_the_permalink( $post ), esc_attr( $attributes['linkTarget'] ), $rel, $title );
+ $title = sprintf( '%4$s', get_the_permalink( $block->context['postId'] ), esc_attr( $attributes['linkTarget'] ), $rel, $title );
}
$classes = array();
diff --git a/packages/block-library/src/preformatted/block.json b/packages/block-library/src/preformatted/block.json
index bab40b94a7ec46..ec6ea839385eb2 100644
--- a/packages/block-library/src/preformatted/block.json
+++ b/packages/block-library/src/preformatted/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/preformatted",
"title": "Preformatted",
"category": "text",
@@ -25,6 +25,10 @@
"text": true
}
},
+ "spacing": {
+ "padding": true,
+ "margin": true
+ },
"typography": {
"fontSize": true,
"lineHeight": true,
diff --git a/packages/block-library/src/preformatted/style.scss b/packages/block-library/src/preformatted/style.scss
index 71e60ffe4ea529..783fee74d4f4fa 100644
--- a/packages/block-library/src/preformatted/style.scss
+++ b/packages/block-library/src/preformatted/style.scss
@@ -1,7 +1,10 @@
.wp-block-preformatted {
+ // This block has customizable padding, border-box makes that more predictable.
+ box-sizing: border-box;
white-space: pre-wrap;
}
-.wp-block-preformatted.has-background {
+// Add low specificity default padding when a background is used.
+:where(.wp-block-preformatted.has-background) {
padding: $block-bg-padding--v $block-bg-padding--h;
}
diff --git a/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap
index 7c0a7aa1dcf48c..db1c80c514206b 100644
--- a/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap
+++ b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap
@@ -39,6 +39,7 @@ exports[`Preformatted should match snapshot when content is empty 1`] = `
onSelectionChange={[Function]}
placeholder="Write preformatted text…"
placeholderTextColor="gray"
+ selectionColor="black"
triggerKeyCodes={[]}
value=""
/>
@@ -85,6 +86,7 @@ exports[`Preformatted should match snapshot when content is not empty 1`] = `
onSelectionChange={[Function]}
placeholder="Write preformatted text…"
placeholderTextColor="gray"
+ selectionColor="black"
triggerKeyCodes={[]}
value="Hello World!
"
/>
diff --git a/packages/block-library/src/preformatted/test/edit.native.js b/packages/block-library/src/preformatted/test/edit.native.js
index 21286a7cdd67df..1fdb4532dacab6 100644
--- a/packages/block-library/src/preformatted/test/edit.native.js
+++ b/packages/block-library/src/preformatted/test/edit.native.js
@@ -24,24 +24,12 @@ import PreformattedEdit from '../edit';
setupCoreBlocks();
describe( 'Preformatted', () => {
- it( 'renders without crashing', () => {
- const screen = render(
-
- );
-
- expect( screen.container ).toBeDefined();
- } );
-
it( 'should match snapshot when content is empty', () => {
const screen = render(
styles1 }
+ getStylesFromColorScheme={ jest.fn() }
/>
);
expect( screen.toJSON() ).toMatchSnapshot();
diff --git a/packages/block-library/src/pullquote/block.json b/packages/block-library/src/pullquote/block.json
index 0732bb52f66bfb..54c4175d3161bd 100644
--- a/packages/block-library/src/pullquote/block.json
+++ b/packages/block-library/src/pullquote/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/pullquote",
"title": "Pullquote",
"category": "text",
diff --git a/packages/block-library/src/pullquote/test/edit.native.js b/packages/block-library/src/pullquote/test/edit.native.js
index 9346750627edba..c53ad24a33f028 100644
--- a/packages/block-library/src/pullquote/test/edit.native.js
+++ b/packages/block-library/src/pullquote/test/edit.native.js
@@ -5,6 +5,7 @@ import {
addBlock,
getBlock,
initializeEditor,
+ selectRangeInRichText,
setupCoreBlocks,
getEditorHtml,
fireEvent,
@@ -45,10 +46,13 @@ describe( 'Pullquote', () => {
const citationTextInput =
within( citationBlock ).getByPlaceholderText( 'Add citation' );
- typeInRichText( citationTextInput, 'A person', {
- finalSelectionStart: 2,
- finalSelectionEnd: 2,
+ typeInRichText( citationTextInput, 'A person' );
+ fireEvent( citationTextInput, 'onKeyDown', {
+ nativeEvent: {},
+ preventDefault() {},
+ keyCode: ENTER,
} );
+ selectRangeInRichText( citationTextInput, 2 );
fireEvent( citationTextInput, 'onKeyDown', {
nativeEvent: {},
preventDefault() {},
@@ -59,7 +63,11 @@ describe( 'Pullquote', () => {
expect( getEditorHtml() ).toMatchInlineSnapshot( `
"
A great statement.
Again
A
person
- "
+
+
+
+
+ "
` );
} );
} );
diff --git a/packages/block-library/src/query-no-results/block.json b/packages/block-library/src/query-no-results/block.json
index 789dcc8e66f605..32088752bb0606 100644
--- a/packages/block-library/src/query-no-results/block.json
+++ b/packages/block-library/src/query-no-results/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query-no-results",
"title": "No results",
"category": "theme",
@@ -9,7 +9,6 @@
"textdomain": "default",
"usesContext": [ "queryId", "query" ],
"supports": {
- "anchor": true,
"align": true,
"reusable": false,
"html": false,
diff --git a/packages/block-library/src/query-no-results/index.php b/packages/block-library/src/query-no-results/index.php
index 4342ba57cccbd7..a6f4bd14d01972 100644
--- a/packages/block-library/src/query-no-results/index.php
+++ b/packages/block-library/src/query-no-results/index.php
@@ -32,14 +32,10 @@ function render_block_core_query_no_results( $attributes, $content, $block ) {
$query = new WP_Query( $query_args );
}
- if ( $query->have_posts() ) {
+ if ( $query->post_count > 0 ) {
return '';
}
- if ( ! $use_global_query ) {
- wp_reset_postdata();
- }
-
$classes = ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) ? 'has-link-color' : '';
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) );
return sprintf(
diff --git a/packages/block-library/src/query-pagination-next/block.json b/packages/block-library/src/query-pagination-next/block.json
index d4861519f149ee..95b1169dc992fd 100644
--- a/packages/block-library/src/query-pagination-next/block.json
+++ b/packages/block-library/src/query-pagination-next/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query-pagination-next",
"title": "Next Page",
"category": "theme",
@@ -12,9 +12,14 @@
"type": "string"
}
},
- "usesContext": [ "queryId", "query", "paginationArrow" ],
+ "usesContext": [
+ "queryId",
+ "query",
+ "paginationArrow",
+ "showLabel",
+ "enhancedPagination"
+ ],
"supports": {
- "anchor": true,
"reusable": false,
"html": false,
"color": {
diff --git a/packages/block-library/src/query-pagination-next/edit.js b/packages/block-library/src/query-pagination-next/edit.js
index c0b4916abccb3d..ce76889f37c750 100644
--- a/packages/block-library/src/query-pagination-next/edit.js
+++ b/packages/block-library/src/query-pagination-next/edit.js
@@ -13,7 +13,7 @@ const arrowMap = {
export default function QueryPaginationNextEdit( {
attributes: { label },
setAttributes,
- context: { paginationArrow },
+ context: { paginationArrow, showLabel },
} ) {
const displayArrow = arrowMap[ paginationArrow ];
return (
@@ -22,16 +22,18 @@ export default function QueryPaginationNextEdit( {
onClick={ ( event ) => event.preventDefault() }
{ ...useBlockProps() }
>
-
- setAttributes( { label: newLabel } )
- }
- />
+ { showLabel && (
+
+ setAttributes( { label: newLabel } )
+ }
+ />
+ ) }
{ displayArrow && (
context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
- $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
- $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0;
+ $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
+ $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination'];
+ $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
+ $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0;
$wrapper_attributes = get_block_wrapper_attributes();
+ $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true;
$default_label = __( 'Next Page' );
- $label = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label;
+ $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label;
+ $label = $show_label ? $label_text : '';
$pagination_arrow = get_query_pagination_arrow( $block, true );
+ if ( ! $label ) {
+ $wrapper_attributes .= ' aria-label="' . $label_text . '"';
+ }
if ( $pagination_arrow ) {
$label .= $pagination_arrow;
}
@@ -31,7 +37,7 @@ function render_block_core_query_pagination_next( $attributes, $content, $block
// Check if the pagination is for Query that inherits the global context.
if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) {
- $filter_link_attributes = function() use ( $wrapper_attributes ) {
+ $filter_link_attributes = static function() use ( $wrapper_attributes ) {
return $wrapper_attributes;
};
add_filter( 'next_posts_link_attributes', $filter_link_attributes );
@@ -56,6 +62,22 @@ function render_block_core_query_pagination_next( $attributes, $content, $block
}
wp_reset_postdata(); // Restore original Post Data.
}
+
+ if ( $enhanced_pagination ) {
+ $p = new WP_HTML_Tag_Processor( $content );
+ if ( $p->next_tag(
+ array(
+ 'tag_name' => 'a',
+ 'class_name' => 'wp-block-query-pagination-next',
+ )
+ ) ) {
+ $p->set_attribute( 'data-wp-key', 'query-pagination-next' );
+ $p->set_attribute( 'data-wp-on--click', 'actions.core.query.navigate' );
+ $p->set_attribute( 'data-wp-on--mouseenter', 'actions.core.query.prefetch' );
+ $content = $p->get_updated_html();
+ }
+ }
+
return $content;
}
diff --git a/packages/block-library/src/query-pagination-numbers/block.json b/packages/block-library/src/query-pagination-numbers/block.json
index a05faff5f1b52b..f05e269d2ece20 100644
--- a/packages/block-library/src/query-pagination-numbers/block.json
+++ b/packages/block-library/src/query-pagination-numbers/block.json
@@ -1,15 +1,20 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query-pagination-numbers",
"title": "Page Numbers",
"category": "theme",
"parent": [ "core/query-pagination" ],
"description": "Displays a list of page numbers for pagination",
"textdomain": "default",
- "usesContext": [ "queryId", "query" ],
+ "attributes": {
+ "midSize": {
+ "type": "number",
+ "default": 2
+ }
+ },
+ "usesContext": [ "queryId", "query", "enhancedPagination" ],
"supports": {
- "anchor": true,
"reusable": false,
"html": false,
"color": {
@@ -33,5 +38,5 @@
}
}
},
- "editorStyle": "query-pagination-numbers-editor"
+ "editorStyle": "wp-block-query-pagination-numbers-editor"
}
diff --git a/packages/block-library/src/query-pagination-numbers/edit.js b/packages/block-library/src/query-pagination-numbers/edit.js
index 3832f673ea1248..eb83204b2cca2b 100644
--- a/packages/block-library/src/query-pagination-numbers/edit.js
+++ b/packages/block-library/src/query-pagination-numbers/edit.js
@@ -1,25 +1,73 @@
/**
* WordPress dependencies
*/
-import { useBlockProps } from '@wordpress/block-editor';
+import { __ } from '@wordpress/i18n';
+import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
+import { PanelBody, RangeControl } from '@wordpress/components';
const createPaginationItem = ( content, Tag = 'a', extraClass = '' ) => (
- { content }
+
+ { content }
+
);
-const previewPaginationNumbers = () => (
- <>
- { createPaginationItem( 1 ) }
- { createPaginationItem( 2 ) }
- { createPaginationItem( 3, 'span', 'current' ) }
- { createPaginationItem( 4 ) }
- { createPaginationItem( 5 ) }
- { createPaginationItem( '...', 'span', 'dots' ) }
- { createPaginationItem( 8 ) }
- >
-);
+const previewPaginationNumbers = ( midSize ) => {
+ const paginationItems = [];
+
+ // First set of pagination items.
+ for ( let i = 1; i <= midSize; i++ ) {
+ paginationItems.push( createPaginationItem( i ) );
+ }
+
+ // Current pagination item.
+ paginationItems.push(
+ createPaginationItem( midSize + 1, 'span', 'current' )
+ );
+
+ // Second set of pagination items.
+ for ( let i = 1; i <= midSize; i++ ) {
+ paginationItems.push( createPaginationItem( midSize + 1 + i ) );
+ }
+
+ // Dots.
+ paginationItems.push( createPaginationItem( '...', 'span', 'dots' ) );
+
+ // Last pagination item.
+ paginationItems.push( createPaginationItem( midSize * 2 + 3 ) );
+
+ return <>{ paginationItems }>;
+};
-export default function QueryPaginationNumbersEdit() {
- const paginationNumbers = previewPaginationNumbers();
- return { paginationNumbers }
;
+export default function QueryPaginationNumbersEdit( {
+ attributes,
+ setAttributes,
+} ) {
+ const { midSize } = attributes;
+ const paginationNumbers = previewPaginationNumbers(
+ parseInt( midSize, 10 )
+ );
+ return (
+ <>
+
+
+ {
+ setAttributes( {
+ midSize: parseInt( value, 10 ),
+ } );
+ } }
+ min={ 0 }
+ max={ 5 }
+ withInputField={ false }
+ />
+
+
+ { paginationNumbers }
+ >
+ );
}
diff --git a/packages/block-library/src/query-pagination-numbers/index.php b/packages/block-library/src/query-pagination-numbers/index.php
index 60fe85efa1f8dc..98098533adac7d 100644
--- a/packages/block-library/src/query-pagination-numbers/index.php
+++ b/packages/block-library/src/query-pagination-numbers/index.php
@@ -15,13 +15,15 @@
* @return string Returns the pagination numbers for the Query.
*/
function render_block_core_query_pagination_numbers( $attributes, $content, $block ) {
- $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
- $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
- $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0;
+ $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
+ $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination'];
+ $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
+ $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0;
$wrapper_attributes = get_block_wrapper_attributes();
$content = '';
global $wp_query;
+ $mid_size = isset( $block->attributes['midSize'] ) ? (int) $block->attributes['midSize'] : null;
if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) {
// Take into account if we have set a bigger `max page`
// than what the query has.
@@ -30,7 +32,10 @@ function render_block_core_query_pagination_numbers( $attributes, $content, $blo
'prev_next' => false,
'total' => $total,
);
- $content = paginate_links( $paginate_args );
+ if ( null !== $mid_size ) {
+ $paginate_args['mid_size'] = $mid_size;
+ }
+ $content = paginate_links( $paginate_args );
} else {
$block_query = new WP_Query( build_query_vars_from_query_block( $block, $page ) );
// `paginate_links` works with the global $wp_query, so we have to
@@ -45,6 +50,9 @@ function render_block_core_query_pagination_numbers( $attributes, $content, $blo
'total' => $total,
'prev_next' => false,
);
+ if ( null !== $mid_size ) {
+ $paginate_args['mid_size'] = $mid_size;
+ }
if ( 1 !== $page ) {
/**
* `paginate_links` doesn't use the provided `format` when the page is `1`.
@@ -77,9 +85,24 @@ function render_block_core_query_pagination_numbers( $attributes, $content, $blo
wp_reset_postdata(); // Restore original Post Data.
$wp_query = $prev_wp_query;
}
+
if ( empty( $content ) ) {
return '';
}
+
+ if ( $enhanced_pagination ) {
+ $p = new WP_HTML_Tag_Processor( $content );
+ while ( $p->next_tag(
+ array(
+ 'tag_name' => 'a',
+ 'class_name' => 'page-numbers',
+ )
+ ) ) {
+ $p->set_attribute( 'data-wp-on--click', 'actions.core.query.navigate' );
+ }
+ $content = $p->get_updated_html();
+ }
+
return sprintf(
'%2$s
',
$wrapper_attributes,
diff --git a/packages/block-library/src/query-pagination-previous/block.json b/packages/block-library/src/query-pagination-previous/block.json
index 823808b0fb054d..fbaac543c1da35 100644
--- a/packages/block-library/src/query-pagination-previous/block.json
+++ b/packages/block-library/src/query-pagination-previous/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query-pagination-previous",
"title": "Previous Page",
"category": "theme",
@@ -12,9 +12,14 @@
"type": "string"
}
},
- "usesContext": [ "queryId", "query", "paginationArrow" ],
+ "usesContext": [
+ "queryId",
+ "query",
+ "paginationArrow",
+ "showLabel",
+ "enhancedPagination"
+ ],
"supports": {
- "anchor": true,
"reusable": false,
"html": false,
"color": {
diff --git a/packages/block-library/src/query-pagination-previous/edit.js b/packages/block-library/src/query-pagination-previous/edit.js
index c863d637f7fb51..3d8b3cfbbef72a 100644
--- a/packages/block-library/src/query-pagination-previous/edit.js
+++ b/packages/block-library/src/query-pagination-previous/edit.js
@@ -13,7 +13,7 @@ const arrowMap = {
export default function QueryPaginationPreviousEdit( {
attributes: { label },
setAttributes,
- context: { paginationArrow },
+ context: { paginationArrow, showLabel },
} ) {
const displayArrow = arrowMap[ paginationArrow ];
return (
@@ -30,16 +30,18 @@ export default function QueryPaginationPreviousEdit( {
{ displayArrow }
) }
-
- setAttributes( { label: newLabel } )
- }
- />
+ { showLabel && (
+
+ setAttributes( { label: newLabel } )
+ }
+ />
+ ) }
);
}
diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php
index 9bb9f0d1f2bfbf..a580880f0f04c8 100644
--- a/packages/block-library/src/query-pagination-previous/index.php
+++ b/packages/block-library/src/query-pagination-previous/index.php
@@ -15,13 +15,19 @@
* @return string Returns the previous posts link for the query.
*/
function render_block_core_query_pagination_previous( $attributes, $content, $block ) {
- $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
- $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
+ $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
+ $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination'];
+ $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
$wrapper_attributes = get_block_wrapper_attributes();
+ $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true;
$default_label = __( 'Previous Page' );
- $label = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label;
+ $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label;
+ $label = $show_label ? $label_text : '';
$pagination_arrow = get_query_pagination_arrow( $block, false );
+ if ( ! $label ) {
+ $wrapper_attributes .= ' aria-label="' . $label_text . '"';
+ }
if ( $pagination_arrow ) {
$label = $pagination_arrow . $label;
}
@@ -29,7 +35,7 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl
// Check if the pagination is for Query that inherits the global context
// and handle appropriately.
if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) {
- $filter_link_attributes = function() use ( $wrapper_attributes ) {
+ $filter_link_attributes = static function() use ( $wrapper_attributes ) {
return $wrapper_attributes;
};
@@ -44,6 +50,22 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl
$label
);
}
+
+ if ( $enhanced_pagination ) {
+ $p = new WP_HTML_Tag_Processor( $content );
+ if ( $p->next_tag(
+ array(
+ 'tag_name' => 'a',
+ 'class_name' => 'wp-block-query-pagination-previous',
+ )
+ ) ) {
+ $p->set_attribute( 'data-wp-key', 'query-pagination-previous' );
+ $p->set_attribute( 'data-wp-on--click', 'actions.core.query.navigate' );
+ $p->set_attribute( 'data-wp-on--mouseenter', 'actions.core.query.prefetch' );
+ $content = $p->get_updated_html();
+ }
+ }
+
return $content;
}
diff --git a/packages/block-library/src/query-pagination/block.json b/packages/block-library/src/query-pagination/block.json
index fa980575ec969d..e32a9ba9b495ff 100644
--- a/packages/block-library/src/query-pagination/block.json
+++ b/packages/block-library/src/query-pagination/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query-pagination",
"title": "Pagination",
"category": "theme",
@@ -11,14 +11,18 @@
"paginationArrow": {
"type": "string",
"default": "none"
+ },
+ "showLabel": {
+ "type": "boolean",
+ "default": true
}
},
"usesContext": [ "queryId", "query" ],
"providesContext": {
- "paginationArrow": "paginationArrow"
+ "paginationArrow": "paginationArrow",
+ "showLabel": "showLabel"
},
"supports": {
- "anchor": true,
"align": true,
"reusable": false,
"html": false,
@@ -31,7 +35,7 @@
"link": true
}
},
- "__experimentalLayout": {
+ "layout": {
"allowSwitching": false,
"allowInheriting": false,
"default": {
diff --git a/packages/block-library/src/query-pagination/edit.js b/packages/block-library/src/query-pagination/edit.js
index 28e6af19ace347..7598eba5c1cacf 100644
--- a/packages/block-library/src/query-pagination/edit.js
+++ b/packages/block-library/src/query-pagination/edit.js
@@ -10,11 +10,13 @@ import {
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { PanelBody } from '@wordpress/components';
+import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { QueryPaginationArrowControls } from './query-pagination-arrow-controls';
+import { QueryPaginationLabelControl } from './query-pagination-label-control';
const TEMPLATE = [
[ 'core/query-pagination-previous' ],
@@ -28,29 +30,38 @@ const ALLOWED_BLOCKS = [
];
export default function QueryPaginationEdit( {
- attributes: { paginationArrow },
+ attributes: { paginationArrow, showLabel },
setAttributes,
clientId,
} ) {
- const hasNextPreviousBlocks = useSelect( ( select ) => {
- const { getBlocks } = select( blockEditorStore );
- const innerBlocks = getBlocks( clientId );
- /**
- * Show the `paginationArrow` control only if a
- * `QueryPaginationNext/Previous` block exists.
- */
- return innerBlocks?.find( ( innerBlock ) => {
- return [
- 'core/query-pagination-next',
- 'core/query-pagination-previous',
- ].includes( innerBlock.name );
- } );
- }, [] );
+ const hasNextPreviousBlocks = useSelect(
+ ( select ) => {
+ const { getBlocks } = select( blockEditorStore );
+ const innerBlocks = getBlocks( clientId );
+ /**
+ * Show the `paginationArrow` and `showLabel` controls only if a
+ * `QueryPaginationNext/Previous` block exists.
+ */
+ return innerBlocks?.find( ( innerBlock ) => {
+ return [
+ 'core/query-pagination-next',
+ 'core/query-pagination-previous',
+ ].includes( innerBlock.name );
+ } );
+ },
+ [ clientId ]
+ );
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
template: TEMPLATE,
allowedBlocks: ALLOWED_BLOCKS,
} );
+ // Always show label text if paginationArrow is set to 'none'.
+ useEffect( () => {
+ if ( paginationArrow === 'none' && ! showLabel ) {
+ setAttributes( { showLabel: true } );
+ }
+ }, [ paginationArrow, setAttributes, showLabel ] );
return (
<>
{ hasNextPreviousBlocks && (
@@ -62,6 +73,14 @@ export default function QueryPaginationEdit( {
setAttributes( { paginationArrow: value } );
} }
/>
+ { paginationArrow !== 'none' && (
+ {
+ setAttributes( { showLabel: value } );
+ } }
+ />
+ ) }
) }
diff --git a/packages/block-library/src/query-pagination/query-pagination-label-control.js b/packages/block-library/src/query-pagination/query-pagination-label-control.js
new file mode 100644
index 00000000000000..9ff80a663adeb5
--- /dev/null
+++ b/packages/block-library/src/query-pagination/query-pagination-label-control.js
@@ -0,0 +1,19 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { ToggleControl } from '@wordpress/components';
+
+export function QueryPaginationLabelControl( { value, onChange } ) {
+ return (
+
+ );
+}
diff --git a/packages/block-library/src/query-title/block.json b/packages/block-library/src/query-title/block.json
index 029762c321e399..2db349e55db90a 100644
--- a/packages/block-library/src/query-title/block.json
+++ b/packages/block-library/src/query-title/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query-title",
"title": "Query Title",
"category": "theme",
@@ -27,7 +27,6 @@
}
},
"supports": {
- "anchor": true,
"align": [ "wide", "full" ],
"html": false,
"color": {
diff --git a/packages/block-library/src/query-title/edit.js b/packages/block-library/src/query-title/edit.js
index da321bead7c0b8..b74e03e7583b2d 100644
--- a/packages/block-library/src/query-title/edit.js
+++ b/packages/block-library/src/query-title/edit.js
@@ -12,14 +12,12 @@ import {
InspectorControls,
useBlockProps,
Warning,
+ HeadingLevelDropdown,
+ store as blockEditorStore,
} from '@wordpress/block-editor';
import { ToggleControl, PanelBody } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import HeadingLevelDropdown from '../heading/heading-level-dropdown';
+import { __, sprintf } from '@wordpress/i18n';
+import { useSelect } from '@wordpress/data';
const SUPPORTED_TYPES = [ 'archive', 'search' ];
@@ -27,6 +25,18 @@ export default function QueryTitleEdit( {
attributes: { type, level, textAlign, showPrefix, showSearchTerm },
setAttributes,
} ) {
+ const { archiveTypeTitle, archiveNameLabel } = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ const {
+ __experimentalArchiveTitleNameLabel,
+ __experimentalArchiveTitleTypeLabel,
+ } = getSettings();
+ return {
+ archiveTypeTitle: __experimentalArchiveTitleTypeLabel,
+ archiveNameLabel: __experimentalArchiveTitleNameLabel,
+ };
+ } );
+
const TagName = `h${ level }`;
const blockProps = useBlockProps( {
className: classnames( 'wp-block-query-title__placeholder', {
@@ -44,6 +54,38 @@ export default function QueryTitleEdit( {
let titleElement;
if ( type === 'archive' ) {
+ let title;
+ if ( archiveTypeTitle ) {
+ if ( showPrefix ) {
+ if ( archiveNameLabel ) {
+ title = sprintf(
+ /* translators: 1: Archive type title e.g: "Category", 2: Label of the archive e.g: "Shoes" */
+ __( '%1$s: %2$s' ),
+ archiveTypeTitle,
+ archiveNameLabel
+ );
+ } else {
+ title = sprintf(
+ /* translators: %s: Archive type title e.g: "Category", "Tag"... */
+ __( '%s: Name' ),
+ archiveTypeTitle
+ );
+ }
+ } else if ( archiveNameLabel ) {
+ title = archiveNameLabel;
+ } else {
+ title = sprintf(
+ /* translators: %s: Archive type title e.g: "Category", "Tag"... */
+ __( '%s name' ),
+ archiveTypeTitle
+ );
+ }
+ } else {
+ title = showPrefix
+ ? __( 'Archive type: Name' )
+ : __( 'Archive title' );
+ }
+
titleElement = (
<>
@@ -58,11 +100,7 @@ export default function QueryTitleEdit( {
/>
-
- { showPrefix
- ? __( 'Archive type: Name' )
- : __( 'Archive title' ) }
-
+
{ title }
>
);
}
@@ -98,7 +136,7 @@ export default function QueryTitleEdit( {
<>
setAttributes( { level: newLevel } )
}
diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json
index bcff0e3ac63b17..d30eccf3765792 100644
--- a/packages/block-library/src/query/block.json
+++ b/packages/block-library/src/query/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/query",
"title": "Query Loop",
"category": "theme",
@@ -32,26 +32,26 @@
"type": "string",
"default": "div"
},
- "displayLayout": {
- "type": "object",
- "default": {
- "type": "list"
- }
- },
"namespace": {
"type": "string"
+ },
+ "enhancedPagination": {
+ "type": "boolean",
+ "default": false
}
},
"providesContext": {
"queryId": "queryId",
"query": "query",
- "displayLayout": "displayLayout"
+ "displayLayout": "displayLayout",
+ "enhancedPagination": "enhancedPagination"
},
"supports": {
"align": [ "wide", "full" ],
- "anchor": true,
"html": false,
- "__experimentalLayout": true
+ "layout": true
},
- "editorStyle": "wp-block-query-editor"
+ "editorStyle": "wp-block-query-editor",
+ "style": "wp-block-query",
+ "viewScript": "file:./view.min.js"
}
diff --git a/packages/block-library/src/query/deprecated.js b/packages/block-library/src/query/deprecated.js
index 05ee6217a288d6..6ec50198973ca6 100644
--- a/packages/block-library/src/query/deprecated.js
+++ b/packages/block-library/src/query/deprecated.js
@@ -12,7 +12,7 @@ import {
/**
* Internal dependencies
*/
-import { unlock } from '../private-apis';
+import { unlock } from '../lock-unlock';
const { cleanEmptyObject } = unlock( blockEditorPrivateApis );
@@ -126,6 +126,86 @@ const migrateColors = ( attributes, innerBlocks ) => {
const hasSingleInnerGroupBlock = ( innerBlocks = [] ) =>
innerBlocks.length === 1 && innerBlocks[ 0 ].name === 'core/group';
+const migrateToConstrainedLayout = ( attributes ) => {
+ const { layout = null } = attributes;
+ if ( ! layout ) {
+ return attributes;
+ }
+ const { inherit = null, contentSize = null, ...newLayout } = layout;
+
+ if ( inherit || contentSize ) {
+ return {
+ ...attributes,
+ layout: {
+ ...newLayout,
+ contentSize,
+ type: 'constrained',
+ },
+ };
+ }
+
+ return attributes;
+};
+
+const findPostTemplateBlock = ( innerBlocks = [] ) => {
+ let foundBlock = null;
+ for ( const block of innerBlocks ) {
+ if ( block.name === 'core/post-template' ) {
+ foundBlock = block;
+ break;
+ } else if ( block.innerBlocks.length ) {
+ foundBlock = findPostTemplateBlock( block.innerBlocks );
+ }
+ }
+ return foundBlock;
+};
+
+const replacePostTemplateBlock = ( innerBlocks = [], replacementBlock ) => {
+ innerBlocks.forEach( ( block, index ) => {
+ if ( block.name === 'core/post-template' ) {
+ innerBlocks.splice( index, 1, replacementBlock );
+ } else if ( block.innerBlocks.length ) {
+ block.innerBlocks = replacePostTemplateBlock(
+ block.innerBlocks,
+ replacementBlock
+ );
+ }
+ } );
+ return innerBlocks;
+};
+
+const migrateDisplayLayout = ( attributes, innerBlocks ) => {
+ const { displayLayout = null, ...newAttributes } = attributes;
+ if ( ! displayLayout ) {
+ return [ attributes, innerBlocks ];
+ }
+ const postTemplateBlock = findPostTemplateBlock( innerBlocks );
+ if ( ! postTemplateBlock ) {
+ return [ attributes, innerBlocks ];
+ }
+
+ const { type, columns } = displayLayout;
+
+ // Convert custom displayLayout values to canonical layout types.
+ const updatedLayoutType = type === 'flex' ? 'grid' : 'default';
+
+ const newPostTemplateBlock = createBlock(
+ 'core/post-template',
+ {
+ ...postTemplateBlock.attributes,
+ layout: {
+ type: updatedLayoutType,
+ ...( columns && { columnCount: columns } ),
+ },
+ },
+ postTemplateBlock.innerBlocks
+ );
+ return [
+ newAttributes,
+ replacePostTemplateBlock( innerBlocks, newPostTemplateBlock ),
+ ];
+};
+
// Version with NO wrapper `div` element.
const v1 = {
attributes: {
@@ -160,13 +240,14 @@ const v1 = {
supports: {
html: false,
},
- migrate( attributes ) {
+ migrate( attributes, innerBlocks ) {
const withTaxQuery = migrateToTaxQuery( attributes );
const { layout, ...restWithTaxQuery } = withTaxQuery;
- return {
+ const newAttributes = {
...restWithTaxQuery,
displayLayout: withTaxQuery.layout,
};
+ return migrateDisplayLayout( newAttributes, innerBlocks );
},
save() {
return ;
@@ -215,13 +296,22 @@ const v2 = {
gradients: true,
link: true,
},
- __experimentalLayout: true,
+ layout: true,
},
isEligible: ( { query: { categoryIds, tagIds } = {} } ) =>
categoryIds || tagIds,
migrate( attributes, innerBlocks ) {
const withTaxQuery = migrateToTaxQuery( attributes );
- return migrateColors( withTaxQuery, innerBlocks );
+ const [ withColorAttributes, withColorInnerBlocks ] = migrateColors(
+ withTaxQuery,
+ innerBlocks
+ );
+ const withConstrainedLayoutAttributes =
+ migrateToConstrainedLayout( withColorAttributes );
+ return migrateDisplayLayout(
+ withConstrainedLayoutAttributes,
+ withColorInnerBlocks
+ );
},
save( { attributes: { tagName: Tag = 'div' } } ) {
const blockProps = useBlockProps.save();
@@ -279,7 +369,7 @@ const v3 = {
text: true,
},
},
- __experimentalLayout: true,
+ layout: true,
},
isEligible( attributes ) {
const { style, backgroundColor, gradient, textColor } = attributes;
@@ -291,7 +381,18 @@ const v3 = {
style?.elements?.link
);
},
- migrate: migrateColors,
+ migrate( attributes, innerBlocks ) {
+ const [ withColorAttributes, withColorInnerBlocks ] = migrateColors(
+ attributes,
+ innerBlocks
+ );
+ const withConstrainedLayoutAttributes =
+ migrateToConstrainedLayout( withColorAttributes );
+ return migrateDisplayLayout(
+ withConstrainedLayoutAttributes,
+ withColorInnerBlocks
+ );
+ },
save( { attributes: { tagName: Tag = 'div' } } ) {
const blockProps = useBlockProps.save();
const innerBlocksProps = useInnerBlocksProps.save( blockProps );
@@ -347,7 +448,7 @@ const v4 = {
text: true,
},
},
- __experimentalLayout: true,
+ layout: true,
},
save( { attributes: { tagName: Tag = 'div' } } ) {
const blockProps = useBlockProps.save();
@@ -355,26 +456,72 @@ const v4 = {
return ;
},
isEligible: ( { layout } ) =>
- ! layout ||
- layout.inherit ||
- ( layout.contentSize && layout.type !== 'constrained' ),
- migrate: ( attributes ) => {
- const { layout = null } = attributes;
- if ( ! layout ) {
- return attributes;
- }
- if ( layout.inherit || layout.contentSize ) {
- return {
- ...attributes,
- layout: {
- ...layout,
- type: 'constrained',
- },
- };
- }
+ layout?.inherit ||
+ ( layout?.contentSize && layout?.type !== 'constrained' ),
+ migrate( attributes, innerBlocks ) {
+ const withConstrainedLayoutAttributes =
+ migrateToConstrainedLayout( attributes );
+ return migrateDisplayLayout(
+ withConstrainedLayoutAttributes,
+ innerBlocks
+ );
+ },
+};
+
+const v5 = {
+ attributes: {
+ queryId: {
+ type: 'number',
+ },
+ query: {
+ type: 'object',
+ default: {
+ perPage: null,
+ pages: 0,
+ offset: 0,
+ postType: 'post',
+ order: 'desc',
+ orderBy: 'date',
+ author: '',
+ search: '',
+ exclude: [],
+ sticky: '',
+ inherit: true,
+ taxQuery: null,
+ parents: [],
+ },
+ },
+ tagName: {
+ type: 'string',
+ default: 'div',
+ },
+ displayLayout: {
+ type: 'object',
+ default: {
+ type: 'list',
+ },
+ },
+ namespace: {
+ type: 'string',
+ },
+ },
+ supports: {
+ align: [ 'wide', 'full' ],
+ anchor: true,
+ html: false,
+ layout: true,
+ },
+ save( { attributes: { tagName: Tag = 'div' } } ) {
+ const blockProps = useBlockProps.save();
+ const innerBlocksProps = useInnerBlocksProps.save( blockProps );
+ return ;
+ },
+ isEligible: ( { displayLayout } ) => {
+ return !! displayLayout;
},
+ migrate: migrateDisplayLayout,
};
-const deprecated = [ v4, v3, v2, v1 ];
+const deprecated = [ v5, v4, v3, v2, v1 ];
export default deprecated;
diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js
index 2222f7d2d1eff3..492f276ccf6151 100644
--- a/packages/block-library/src/query/edit/inspector-controls/index.js
+++ b/packages/block-library/src/query/edit/inspector-controls/index.js
@@ -17,7 +17,8 @@ import {
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { debounce } from '@wordpress/compose';
-import { useEffect, useState, useCallback } from '@wordpress/element';
+import { useEffect, useState, useCallback, useRef } from '@wordpress/element';
+import { speak } from '@wordpress/a11y';
/**
* Internal dependencies
@@ -28,7 +29,7 @@ import ParentControl from './parent-control';
import { TaxonomyControls } from './taxonomy-controls';
import StickyControl from './sticky-control';
import CreateNewPostLink from './create-new-post-link';
-import { unlock } from '../../../private-apis';
+import { unlock } from '../../../lock-unlock';
import {
usePostTypes,
useIsPostTypeHierarchical,
@@ -40,8 +41,8 @@ import {
const { BlockInfo } = unlock( blockEditorPrivateApis );
export default function QueryInspectorControls( props ) {
- const { attributes, setQuery, setDisplayLayout } = props;
- const { query, displayLayout } = attributes;
+ const { attributes, setQuery, setDisplayLayout, setAttributes } = props;
+ const { query, displayLayout, enhancedPagination } = attributes;
const {
order,
orderBy,
@@ -101,7 +102,7 @@ export default function QueryInspectorControls( props ) {
const showInheritControl = isControlAllowed( allowedControls, 'inherit' );
const showPostTypeControl =
! inherit && isControlAllowed( allowedControls, 'postType' );
- const showColumnsControl = displayLayout?.type === 'flex';
+ const showColumnsControl = false;
const showOrderControl =
! inherit && isControlAllowed( allowedControls, 'order' );
const showStickyControl =
@@ -123,6 +124,18 @@ export default function QueryInspectorControls( props ) {
isControlAllowed( allowedControls, 'parents' ) &&
isPostTypeHierarchical;
+ const enhancedPaginationNotice = __(
+ 'Enhanced Pagination might cause interactive blocks within the Post Template to stop working. Disable it if you experience any issues.'
+ );
+
+ const isFirstRender = useRef( true ); // Don't speak on first render.
+ useEffect( () => {
+ if ( ! isFirstRender.current && enhancedPagination ) {
+ speak( enhancedPaginationNotice );
+ }
+ isFirstRender.current = false;
+ }, [ enhancedPagination, enhancedPaginationNotice ] );
+
const showFiltersPanel =
showTaxControl ||
showAuthorControl ||
@@ -169,7 +182,9 @@ export default function QueryInspectorControls( props ) {
label={ __( 'Columns' ) }
value={ displayLayout.columns }
onChange={ ( value ) =>
- setDisplayLayout( { columns: value } )
+ setDisplayLayout( {
+ columns: value,
+ } )
}
min={ 2 }
max={ Math.max( 6, displayLayout.columns ) }
@@ -278,6 +293,36 @@ export default function QueryInspectorControls( props ) {
) }
+
+
+
+ setAttributes( {
+ enhancedPagination: !! value,
+ } )
+ }
+ />
+ { enhancedPagination && (
+
+
+ { enhancedPaginationNotice }
+
+
+ ) }
+
+
>
);
}
diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js
index 9563673917f57e..89c6efa2809795 100644
--- a/packages/block-library/src/query/edit/query-content.js
+++ b/packages/block-library/src/query/edit/query-content.js
@@ -13,6 +13,7 @@ import {
} from '@wordpress/block-editor';
import { SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
+import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
@@ -35,6 +36,7 @@ export default function QueryContent( {
query,
displayLayout,
tagName: TagName = 'div',
+ query: { inherit } = {},
} = attributes;
const { __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
@@ -45,9 +47,12 @@ export default function QueryContent( {
} );
const { postsPerPage } = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
+ const { getEntityRecord, canUser } = select( coreStore );
+ const settingPerPage = canUser( 'read', 'settings' )
+ ? +getEntityRecord( 'root', 'site' )?.posts_per_page
+ : +getSettings().postsPerPage;
return {
- postsPerPage:
- +getSettings().postsPerPage || DEFAULTS_POSTS_PER_PAGE,
+ postsPerPage: settingPerPage || DEFAULTS_POSTS_PER_PAGE,
};
}, [] );
// There are some effects running where some initialization logic is
@@ -61,14 +66,18 @@ export default function QueryContent( {
// would cause to override previous wanted changes.
useEffect( () => {
const newQuery = {};
- if ( ! query.perPage && postsPerPage ) {
+ // When we inherit from global query always need to set the `perPage`
+ // based on the reading settings.
+ if ( inherit && query.perPage !== postsPerPage ) {
+ newQuery.perPage = postsPerPage;
+ } else if ( ! query.perPage && postsPerPage ) {
newQuery.perPage = postsPerPage;
}
if ( !! Object.keys( newQuery ).length ) {
__unstableMarkNextChangeAsNotPersistent();
updateQuery( newQuery );
}
- }, [ query.perPage ] );
+ }, [ query.perPage, postsPerPage, inherit ] );
// We need this for multi-query block pagination.
// Query parameters for each block are scoped to their ID.
useEffect( () => {
@@ -100,6 +109,7 @@ export default function QueryContent( {
attributes={ attributes }
setQuery={ updateQuery }
setDisplayLayout={ updateDisplayLayout }
+ setAttributes={ setAttributes }
/>
diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js
index 1d079eb399fb80..7b02290ae4c76c 100644
--- a/packages/block-library/src/query/edit/query-toolbar.js
+++ b/packages/block-library/src/query/edit/query-toolbar.js
@@ -10,7 +10,7 @@ import {
} from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
-import { settings, list, grid } from '@wordpress/icons';
+import { settings } from '@wordpress/icons';
/**
* Internal dependencies
@@ -18,9 +18,8 @@ import { settings, list, grid } from '@wordpress/icons';
import { usePatterns } from '../utils';
export default function QueryToolbar( {
- attributes: { query, displayLayout },
+ attributes: { query },
setQuery,
- setDisplayLayout,
openPatternSelectionModal,
name,
clientId,
@@ -30,24 +29,7 @@ export default function QueryToolbar( {
QueryToolbar,
'blocks-query-pagination-max-page-input'
);
- const displayLayoutControls = [
- {
- icon: list,
- title: __( 'List view' ),
- onClick: () => setDisplayLayout( { type: 'list' } ),
- isActive: displayLayout?.type === 'list',
- },
- {
- icon: grid,
- title: __( 'Grid view' ),
- onClick: () =>
- setDisplayLayout( {
- type: 'flex',
- columns: displayLayout?.columns || 3,
- } ),
- isActive: displayLayout?.type === 'flex',
- },
- ];
+
return (
<>
{ ! query.inherit && (
@@ -144,7 +126,6 @@ export default function QueryToolbar( {
) }
-
>
);
}
diff --git a/packages/block-library/src/query/index.php b/packages/block-library/src/query/index.php
index 2d22338a97df3e..f06073cc952000 100644
--- a/packages/block-library/src/query/index.php
+++ b/packages/block-library/src/query/index.php
@@ -5,12 +5,105 @@
* @package WordPress
*/
+/**
+ * Modifies the static `core/query` block on the server.
+ *
+ * @since X.X.X
+ *
+ * @param array $attributes Block attributes.
+ * @param string $content Block default content.
+ * @param string $block Block instance.
+ *
+ * @return string Returns the modified output of the query block.
+ */
+function render_block_core_query( $attributes, $content, $block ) {
+ if ( $attributes['enhancedPagination'] ) {
+ $p = new WP_HTML_Tag_Processor( $content );
+ if ( $p->next_tag() ) {
+ // Add the necessary directives.
+ $p->set_attribute( 'data-wp-interactive', true );
+ $p->set_attribute( 'data-wp-navigation-id', 'query-' . $attributes['queryId'] );
+ $p->set_attribute(
+ 'data-wp-context',
+ wp_json_encode( array( 'core' => array( 'query' => (object) array() ) ) )
+ );
+ $content = $p->get_updated_html();
+
+ // Mark the block as interactive.
+ $block->block_type->supports['interactivity'] = true;
+
+ // Add a div to announce messages using `aria-live`.
+ $last_div_position = strripos( $content, '' );
+ $content = substr_replace(
+ $content,
+ '
+ ',
+ $last_div_position,
+ 0
+ );
+
+ // Use state to send translated strings.
+ wp_store(
+ array(
+ 'state' => array(
+ 'core' => array(
+ 'query' => array(
+ 'loadingText' => __( 'Loading page, please wait.' ),
+ 'loadedText' => __( 'Page Loaded.' ),
+ ),
+ ),
+ ),
+ )
+ );
+ }
+ }
+
+ $view_asset = 'wp-block-query-view';
+ if ( ! wp_script_is( $view_asset ) ) {
+ $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 ( ! $attributes['enhancedPagination'] && in_array( $view_asset, $script_handles, true ) ) {
+ $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_asset ) );
+ }
+ // If the script is needed, but it was previously removed, add it again.
+ if ( $attributes['enhancedPagination'] && ! in_array( $view_asset, $script_handles, true ) ) {
+ $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_asset ) );
+ }
+ }
+
+ $style_asset = 'wp-block-query';
+ if ( ! wp_style_is( $style_asset ) ) {
+ $style_handles = $block->block_type->style_handles;
+ // If the styles are not needed, and they are still in the `style_handles`, remove them.
+ if ( ! $attributes['enhancedPagination'] && in_array( $style_asset, $style_handles, true ) ) {
+ $block->block_type->style_handles = array_diff( $style_handles, array( $style_asset ) );
+ }
+ // If the styles are needed, but they were previously removed, add them again.
+ if ( $attributes['enhancedPagination'] && ! in_array( $style_asset, $style_handles, true ) ) {
+ $block->block_type->style_handles = array_merge( $style_handles, array( $style_asset ) );
+ }
+ }
+
+ return $content;
+}
+
/**
* Registers the `core/query` block on the server.
*/
function register_block_core_query() {
register_block_type_from_metadata(
- __DIR__ . '/query'
+ __DIR__ . '/query',
+ array(
+ 'render_callback' => 'render_block_core_query',
+ )
);
}
add_action( 'init', 'register_block_core_query' );
diff --git a/packages/block-library/src/query/style.scss b/packages/block-library/src/query/style.scss
new file mode 100644
index 00000000000000..c560018056d7f0
--- /dev/null
+++ b/packages/block-library/src/query/style.scss
@@ -0,0 +1,63 @@
+.wp-block-query__enhanced-pagination-animation {
+ position: fixed;
+ top: 0;
+ left: 0;
+ margin: 0;
+ padding: 0;
+ width: 100vw;
+ max-width: 100vw !important;
+ height: 4px;
+ background-color: var(--wp--preset--color--primary, #000);
+ opacity: 0;
+
+ &.start-animation {
+ animation:
+ wp-block-query__enhanced-pagination-start-animation
+ 30s
+ cubic-bezier(0, 1, 0, 1)
+ infinite;
+ }
+
+ &.finish-animation {
+ animation:
+ wp-block-query__enhanced-pagination-finish-animation
+ 300ms
+ ease-in;
+ }
+}
+
+@keyframes wp-block-query__enhanced-pagination-start-animation {
+ 0% {
+ transform: scaleX(0);
+ transform-origin: 0% 0%;
+ opacity: 1;
+ }
+ 100% {
+ transform: scaleX(1);
+ transform-origin: 0% 0%;
+ opacity: 1;
+ }
+}
+
+@keyframes wp-block-query__enhanced-pagination-finish-animation {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.wp-block-query__enhanced-pagination-navigation-announce {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ border: 0;
+}
diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js
index 9bd88de020cc83..4e787be1d029f5 100644
--- a/packages/block-library/src/query/utils.js
+++ b/packages/block-library/src/query/utils.js
@@ -8,11 +8,6 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
import { decodeEntities } from '@wordpress/html-entities';
import { cloneBlock, store as blocksStore } from '@wordpress/blocks';
-/**
- * Internal dependencies
- */
-import { name as queryLoopName } from './block.json';
-
/** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */
/**
@@ -175,7 +170,7 @@ export function useAllowedControls( attributes ) {
return useSelect(
( select ) =>
select( blocksStore ).getActiveBlockVariation(
- queryLoopName,
+ 'core/query',
attributes
)?.allowedControls,
@@ -249,25 +244,29 @@ export function useBlockNameForPatterns( clientId, attributes ) {
const activeVariationName = useSelect(
( select ) =>
select( blocksStore ).getActiveBlockVariation(
- queryLoopName,
+ 'core/query',
attributes
)?.name,
[ attributes ]
);
- const blockName = `${ queryLoopName }/${ activeVariationName }`;
- const activeVariationPatterns = useSelect(
+ const blockName = `core/query/${ activeVariationName }`;
+ const hasActiveVariationPatterns = useSelect(
( select ) => {
if ( ! activeVariationName ) {
- return;
+ return false;
}
const { getBlockRootClientId, getPatternsByBlockTypes } =
select( blockEditorStore );
const rootClientId = getBlockRootClientId( clientId );
- return getPatternsByBlockTypes( blockName, rootClientId );
+ const activePatterns = getPatternsByBlockTypes(
+ blockName,
+ rootClientId
+ );
+ return activePatterns.length > 0;
},
- [ clientId, activeVariationName ]
+ [ clientId, activeVariationName, blockName ]
);
- return activeVariationPatterns?.length ? blockName : queryLoopName;
+ return hasActiveVariationPatterns ? blockName : 'core/query';
}
/**
@@ -300,10 +299,10 @@ export function useScopedBlockVariations( attributes ) {
select( blocksStore );
return {
activeVariationName: getActiveBlockVariation(
- queryLoopName,
+ 'core/query',
attributes
)?.name,
- blockVariations: getBlockVariations( queryLoopName, 'block' ),
+ blockVariations: getBlockVariations( 'core/query', 'block' ),
};
},
[ attributes ]
diff --git a/packages/block-library/src/query/view.js b/packages/block-library/src/query/view.js
new file mode 100644
index 00000000000000..cbd5573e05c6f9
--- /dev/null
+++ b/packages/block-library/src/query/view.js
@@ -0,0 +1,82 @@
+/**
+ * WordPress dependencies
+ */
+import { store, navigate, prefetch } from '@wordpress/interactivity';
+
+const isValidLink = ( ref ) =>
+ ref &&
+ ref instanceof window.HTMLAnchorElement &&
+ ref.href &&
+ ( ! ref.target || ref.target === '_self' ) &&
+ ref.origin === window.location.origin;
+
+const isValidEvent = ( event ) =>
+ event.button === 0 && // left clicks only
+ ! event.metaKey && // open in new tab (mac)
+ ! event.ctrlKey && // open in new tab (windows)
+ ! event.altKey && // download
+ ! event.shiftKey &&
+ ! event.defaultPrevented;
+
+store( {
+ selectors: {
+ core: {
+ query: {
+ startAnimation: ( { context } ) =>
+ context.core.query.animation === 'start',
+ finishAnimation: ( { context } ) =>
+ context.core.query.animation === 'finish',
+ },
+ },
+ },
+ actions: {
+ core: {
+ query: {
+ navigate: async ( { event, ref, context, state } ) => {
+ if ( isValidLink( ref ) && isValidEvent( event ) ) {
+ event.preventDefault();
+
+ const id = ref.closest( '[data-wp-navigation-id]' )
+ .dataset.wpNavigationId;
+
+ // Don't announce the navigation immediately, wait 300 ms.
+ const timeout = setTimeout( () => {
+ context.core.query.message =
+ state.core.query.loadingText;
+ context.core.query.animation = 'start';
+ }, 300 );
+
+ await navigate( ref.href );
+
+ // Dismiss loading message if it hasn't been added yet.
+ clearTimeout( timeout );
+
+ // Announce that the page has been loaded. If the message is the
+ // same, we use a no-break space similar to the @wordpress/a11y
+ // package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
+ context.core.query.message =
+ state.core.query.loadedText +
+ ( context.core.query.message ===
+ state.core.query.loadedText
+ ? '\u00A0'
+ : '' );
+
+ context.core.query.animation = 'finish';
+
+ // Focus the first anchor of the Query block.
+ document
+ .querySelector(
+ `[data-wp-navigation-id=${ id }] a[href]`
+ )
+ ?.focus();
+ }
+ },
+ prefetch: async ( { ref } ) => {
+ if ( isValidLink( ref ) ) {
+ await prefetch( ref.href );
+ }
+ },
+ },
+ },
+ },
+} );
diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json
index 485774ceb0a9cf..eff4649230a580 100644
--- a/packages/block-library/src/quote/block.json
+++ b/packages/block-library/src/quote/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/quote",
"title": "Quote",
"category": "text",
@@ -31,6 +31,7 @@
"anchor": true,
"html": false,
"__experimentalOnEnter": true,
+ "__experimentalOnMerge": true,
"typography": {
"fontSize": true,
"lineHeight": true,
@@ -47,6 +48,7 @@
},
"color": {
"gradients": true,
+ "heading": true,
"link": true,
"__experimentalDefaultControls": {
"background": true,
diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js
index e6c560078e5010..32c99bf7d097db 100644
--- a/packages/block-library/src/quote/edit.js
+++ b/packages/block-library/src/quote/edit.js
@@ -93,6 +93,7 @@ export default function QuoteEdit( {
const innerBlocksProps = useInnerBlocksProps( blockProps, {
template: TEMPLATE,
templateInsertUpdatesSelection: true,
+ __experimentalCaptureToolbars: true,
} );
return (
diff --git a/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap
index 5b5df918f2beeb..65d87d5b0d7bd4 100644
--- a/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap
+++ b/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap
@@ -22,6 +22,16 @@ exports[`Quote block transforms to Group block 1`] = `
"
`;
+exports[`Quote block transforms to Paragraph block 1`] = `
+"
+
"This will make running your own blog a viable alternative again."
+
+
+
+
— Adrian Zumbrunnen
+"
+`;
+
exports[`Quote block transforms to Pullquote block 1`] = `
"
"This will make running your own blog a viable alternative again."
— Adrian Zumbrunnen
diff --git a/packages/block-library/src/quote/test/edit.native.js b/packages/block-library/src/quote/test/edit.native.js
index 388ef1b8d15f77..d2f2aad6669f98 100644
--- a/packages/block-library/src/quote/test/edit.native.js
+++ b/packages/block-library/src/quote/test/edit.native.js
@@ -5,6 +5,7 @@ import {
addBlock,
getBlock,
initializeEditor,
+ selectRangeInRichText,
setupCoreBlocks,
getEditorHtml,
fireEvent,
@@ -62,10 +63,13 @@ describe( 'Quote', () => {
typeInRichText( quoteTextInput, 'Again.' );
const citationTextInput =
within( citationBlock ).getByPlaceholderText( 'Add citation' );
- typeInRichText( citationTextInput, 'A person', {
- finalSelectionStart: 2,
- finalSelectionEnd: 2,
+ typeInRichText( citationTextInput, 'A person' );
+ fireEvent( citationTextInput, 'onKeyDown', {
+ nativeEvent: {},
+ preventDefault() {},
+ keyCode: ENTER,
} );
+ selectRangeInRichText( citationTextInput, 2 );
fireEvent( citationTextInput, 'onKeyDown', {
nativeEvent: {},
preventDefault() {},
@@ -82,7 +86,11 @@ describe( 'Quote', () => {
Again.
A
person
- "
+
+
+
+
+ "
` );
} );
} );
diff --git a/packages/block-library/src/quote/test/transforms.native.js b/packages/block-library/src/quote/test/transforms.native.js
index 46c4eb2b6f9727..25030e0a018d41 100644
--- a/packages/block-library/src/quote/test/transforms.native.js
+++ b/packages/block-library/src/quote/test/transforms.native.js
@@ -21,7 +21,11 @@ const initialHtml = `
`;
const transformsWithInnerBlocks = [ 'Columns', 'Group' ];
-const blockTransforms = [ 'Pullquote', ...transformsWithInnerBlocks ];
+const blockTransforms = [
+ 'Pullquote',
+ 'Paragraph',
+ ...transformsWithInnerBlocks,
+];
setupCoreBlocks();
diff --git a/packages/block-library/src/quote/transforms.js b/packages/block-library/src/quote/transforms.js
index d4cd77177bf030..4e153a6399029f 100644
--- a/packages/block-library/src/quote/transforms.js
+++ b/packages/block-library/src/quote/transforms.js
@@ -109,6 +109,19 @@ const transforms = {
} );
},
},
+ {
+ type: 'block',
+ blocks: [ 'core/paragraph' ],
+ transform: ( { citation }, innerBlocks ) =>
+ citation
+ ? [
+ ...innerBlocks,
+ createBlock( 'core/paragraph', {
+ content: citation,
+ } ),
+ ]
+ : innerBlocks,
+ },
{
type: 'block',
blocks: [ 'core/group' ],
diff --git a/packages/block-library/src/read-more/block.json b/packages/block-library/src/read-more/block.json
index ed2b23c3b7f0fd..d3386a49d66b82 100644
--- a/packages/block-library/src/read-more/block.json
+++ b/packages/block-library/src/read-more/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/read-more",
"title": "Read More",
"category": "theme",
@@ -17,7 +17,6 @@
},
"usesContext": [ "postId" ],
"supports": {
- "anchor": true,
"html": false,
"color": {
"gradients": true,
diff --git a/packages/block-library/src/rss/block.json b/packages/block-library/src/rss/block.json
index 2e3fd4b2d385e1..2535eda5946fbd 100644
--- a/packages/block-library/src/rss/block.json
+++ b/packages/block-library/src/rss/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/rss",
"title": "RSS",
"category": "widgets",
@@ -43,7 +43,6 @@
},
"supports": {
"align": true,
- "anchor": true,
"html": false
},
"editorStyle": "wp-block-rss-editor",
diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js
index 2fd94cdd2a021a..d24de5c291d511 100644
--- a/packages/block-library/src/rss/edit.js
+++ b/packages/block-library/src/rss/edit.js
@@ -116,6 +116,7 @@ export default function RSSEdit( { attributes, setAttributes } ) {
@@ -146,6 +147,7 @@ export default function RSSEdit( { attributes, setAttributes } ) {
{ displayExcerpt && (
@@ -159,6 +161,7 @@ export default function RSSEdit( { attributes, setAttributes } ) {
{ blockLayout === 'grid' && (
diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json
index 387295ebb36dea..b2873bfa8e5729 100644
--- a/packages/block-library/src/search/block.json
+++ b/packages/block-library/src/search/block.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
+ "apiVersion": 3,
"name": "core/search",
"title": "Search",
"category": "widgets",
@@ -42,11 +42,18 @@
"query": {
"type": "object",
"default": {}
+ },
+ "buttonBehavior": {
+ "type": "string",
+ "default": "expand-searchfield"
+ },
+ "isSearchFieldHidden": {
+ "type": "boolean",
+ "default": false
}
},
"supports": {
"align": [ "left", "center", "right" ],
- "anchor": true,
"color": {
"gradients": true,
"__experimentalSkipSerialization": true,
@@ -83,6 +90,7 @@
},
"html": false
},
+ "viewScript": "file:./view.min.js",
"editorStyle": "wp-block-search-editor",
"style": "wp-block-search"
}
diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js
index 5d82ced145926f..ff957b575c7a4f 100644
--- a/packages/block-library/src/search/edit.js
+++ b/packages/block-library/src/search/edit.js
@@ -19,7 +19,7 @@ import {
useSetting,
} from '@wordpress/block-editor';
import { useDispatch, useSelect } from '@wordpress/data';
-import { useEffect } from '@wordpress/element';
+import { useEffect, useRef } from '@wordpress/element';
import {
ToolbarDropdownMenu,
ToolbarGroup,
@@ -52,13 +52,15 @@ import {
PC_WIDTH_DEFAULT,
PX_WIDTH_DEFAULT,
MIN_WIDTH,
- MIN_WIDTH_UNIT,
+ isPercentageUnit,
} from './utils.js';
// Used to calculate border radius adjustment to avoid "fat" corners when
// button is placed inside wrapper.
const DEFAULT_INNER_PADDING = '4px';
+const BUTTON_BEHAVIOR_EXPAND = 'expand-searchfield';
+
export default function SearchEdit( {
className,
attributes,
@@ -77,6 +79,8 @@ export default function SearchEdit( {
buttonText,
buttonPosition,
buttonUseIcon,
+ buttonBehavior,
+ isSearchFieldHidden,
style,
} = attributes;
@@ -93,8 +97,8 @@ export default function SearchEdit( {
);
const { __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
- useEffect( () => {
- if ( ! insertedInNavigationBlock ) return;
+
+ if ( insertedInNavigationBlock ) {
// This side-effect should not create an undo level.
__unstableMarkNextChangeAsNotPersistent();
setAttributes( {
@@ -102,7 +106,8 @@ export default function SearchEdit( {
buttonUseIcon: true,
buttonPosition: 'button-inside',
} );
- }, [ insertedInNavigationBlock ] );
+ }
+
const borderRadius = style?.border?.radius;
const borderProps = useBorderProps( attributes );
@@ -130,12 +135,33 @@ export default function SearchEdit( {
const isButtonPositionOutside = 'button-outside' === buttonPosition;
const hasNoButton = 'no-button' === buttonPosition;
const hasOnlyButton = 'button-only' === buttonPosition;
+ const searchFieldRef = useRef();
+ const buttonRef = useRef();
const units = useCustomUnits( {
availableUnits: [ '%', 'px' ],
defaultValues: { '%': PC_WIDTH_DEFAULT, px: PX_WIDTH_DEFAULT },
} );
+ useEffect( () => {
+ if ( hasOnlyButton && ! isSelected ) {
+ setAttributes( {
+ isSearchFieldHidden: true,
+ } );
+ }
+ }, [ hasOnlyButton, isSelected, setAttributes ] );
+
+ // Show the search field when width changes.
+ useEffect( () => {
+ if ( ! hasOnlyButton || ! isSelected ) {
+ return;
+ }
+
+ setAttributes( {
+ isSearchFieldHidden: false,
+ } );
+ }, [ hasOnlyButton, isSelected, setAttributes, width ] );
+
const getBlockClassNames = () => {
return classnames(
className,
@@ -152,6 +178,12 @@ export default function SearchEdit( {
: undefined,
buttonUseIcon && ! hasNoButton
? 'wp-block-search__icon-button'
+ : undefined,
+ hasOnlyButton && BUTTON_BEHAVIOR_EXPAND === buttonBehavior
+ ? 'wp-block-search__button-behavior-expand'
+ : undefined,
+ hasOnlyButton && isSearchFieldHidden
+ ? 'wp-block-search__searchfield-hidden'
: undefined
);
};
@@ -165,6 +197,7 @@ export default function SearchEdit( {
onClick: () => {
setAttributes( {
buttonPosition: 'button-outside',
+ isSearchFieldHidden: false,
} );
},
},
@@ -176,6 +209,7 @@ export default function SearchEdit( {
onClick: () => {
setAttributes( {
buttonPosition: 'button-inside',
+ isSearchFieldHidden: false,
} );
},
},
@@ -187,6 +221,19 @@ export default function SearchEdit( {
onClick: () => {
setAttributes( {
buttonPosition: 'no-button',
+ isSearchFieldHidden: false,
+ } );
+ },
+ },
+ {
+ role: 'menuitemradio',
+ title: __( 'Button only' ),
+ isActive: buttonPosition === 'button-only',
+ icon: buttonOnly,
+ onClick: () => {
+ setAttributes( {
+ buttonPosition: 'button-only',
+ isSearchFieldHidden: true,
} );
},
},
@@ -247,6 +294,7 @@ export default function SearchEdit( {
onChange={ ( event ) =>
setAttributes( { placeholder: event.target.value } )
}
+ ref={ searchFieldRef }
/>
);
};
@@ -268,6 +316,13 @@ export default function SearchEdit( {
? { borderRadius }
: borderProps.style ),
};
+ const handleButtonClick = () => {
+ if ( hasOnlyButton && BUTTON_BEHAVIOR_EXPAND === buttonBehavior ) {
+ setAttributes( {
+ isSearchFieldHidden: ! isSearchFieldHidden,
+ } );
+ }
+ };
return (
<>
@@ -281,6 +336,8 @@ export default function SearchEdit( {
? stripHTML( buttonText )
: __( 'Search' )
}
+ onClick={ handleButtonClick }
+ ref={ buttonRef }
>
@@ -297,6 +354,7 @@ export default function SearchEdit( {
onChange={ ( html ) =>
setAttributes( { buttonText: html } )
}
+ onClick={ handleButtonClick }
/>
) }
>
@@ -347,7 +405,13 @@ export default function SearchEdit( {
>
{
const filteredWidth =
widthUnit === '%' &&
@@ -383,8 +447,8 @@ export default function SearchEdit( {
key={ widthValue }
isSmall
variant={
- `${ widthValue }%` ===
- `${ width }${ widthUnit }`
+ widthValue === width &&
+ widthUnit === '%'
? 'primary'
: undefined
}
@@ -516,14 +580,15 @@ export default function SearchEdit( {
} }
showHandle={ isSelected }
>
- { ( isButtonPositionInside || isButtonPositionOutside ) && (
+ { ( isButtonPositionInside ||
+ isButtonPositionOutside ||
+ hasOnlyButton ) && (
<>
{ renderTextField() }
{ renderButton() }
>
) }
- { hasOnlyButton && renderButton() }
{ hasNoButton && renderTextField() }
diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php
index f42622551fc562..670ceb0eb66c54 100644
--- a/packages/block-library/src/search/index.php
+++ b/packages/block-library/src/search/index.php
@@ -8,11 +8,15 @@
/**
* Dynamically renders the `core/search` block.
*
- * @param array $attributes The block attributes.
+ * @since 6.3.0 Using block.json `viewScript` to register script, and update `view_script_handles()` only when needed.
+ *
+ * @param array $attributes The block attributes.
+ * @param string $content The saved content.
+ * @param WP_Block $block The parsed block.
*
* @return string The search block markup.
*/
-function render_block_core_search( $attributes ) {
+function render_block_core_search( $attributes, $content, $block ) {
// Older versions of the Search block defaulted the label and buttonText
// attributes to `__( 'Search' )` meaning that many posts contain ``. Support these by defaulting an undefined label and
@@ -29,11 +33,11 @@ function render_block_core_search( $attributes ) {
$classnames = classnames_for_block_core_search( $attributes );
$show_label = ( ! empty( $attributes['showLabel'] ) ) ? true : false;
$use_icon_button = ( ! empty( $attributes['buttonUseIcon'] ) ) ? true : false;
- $show_input = ( ! empty( $attributes['buttonPosition'] ) && 'button-only' === $attributes['buttonPosition'] ) ? false : true;
$show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true;
+ $button_position = $show_button ? $attributes['buttonPosition'] : null;
$query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array();
- $input_markup = '';
- $button_markup = '';
+ $button_behavior = ( ! empty( $attributes['buttonBehavior'] ) ) ? $attributes['buttonBehavior'] : 'default';
+ $button = '';
$query_params_markup = '';
$inline_styles = styles_for_block_core_search( $attributes );
$color_classes = get_color_classes_for_block_core_search( $attributes );
@@ -44,42 +48,53 @@ function render_block_core_search( $attributes ) {
$border_color_classes = get_border_color_classes_for_block_core_search( $attributes );
$label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] );
-
- $label_markup = sprintf(
- '
',
- esc_attr( $input_id ),
- $label_inner_html
- );
- if ( $show_label && ! empty( $attributes['label'] ) ) {
- $label_classes = array( 'wp-block-search__label' );
- if ( ! empty( $typography_classes ) ) {
- $label_classes[] = $typography_classes;
+ $label = new WP_HTML_Tag_Processor( sprintf( '
', $inline_styles['label'], $label_inner_html ) );
+ if ( $label->next_tag() ) {
+ $label->set_attribute( 'for', $input_id );
+ $label->add_class( 'wp-block-search__label' );
+ if ( $show_label && ! empty( $attributes['label'] ) ) {
+ if ( ! empty( $typography_classes ) ) {
+ $label->add_class( $typography_classes );
+ }
+ } else {
+ $label->add_class( 'screen-reader-text' );
}
- $label_markup = sprintf(
- '
',
- esc_attr( $input_id ),
- esc_attr( implode( ' ', $label_classes ) ),
- $inline_styles['label'],
- $label_inner_html
- );
}
- if ( $show_input ) {
- $input_classes = array( 'wp-block-search__input' );
- if ( ! $is_button_inside && ! empty( $border_color_classes ) ) {
- $input_classes[] = $border_color_classes;
+ $input = new WP_HTML_Tag_Processor( sprintf( '
', $inline_styles['input'] ) );
+ $input_classes = array( 'wp-block-search__input' );
+ if ( ! $is_button_inside && ! empty( $border_color_classes ) ) {
+ $input_classes[] = $border_color_classes;
+ }
+ if ( ! empty( $typography_classes ) ) {
+ $input_classes[] = $typography_classes;
+ }
+ if ( $input->next_tag() ) {
+ $input->add_class( implode( ' ', $input_classes ) );
+ $input->set_attribute( 'id', $input_id );
+ $input->set_attribute( 'value', get_search_query() );
+ $input->set_attribute( 'placeholder', $attributes['placeholder'] );
+
+ $is_expandable_searchfield = 'button-only' === $button_position && 'expand-searchfield' === $button_behavior;
+ if ( $is_expandable_searchfield ) {
+ $input->set_attribute( 'aria-hidden', 'true' );
+ $input->set_attribute( 'tabindex', '-1' );
}
- if ( ! empty( $typography_classes ) ) {
- $input_classes[] = $typography_classes;
+
+ // If the script already exists, there is no point in removing it from viewScript.
+ $view_js_file = 'wp-block-search-view';
+ 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_expandable_searchfield && 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 ( $is_expandable_searchfield && ! in_array( $view_js_file, $script_handles, true ) ) {
+ $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) );
+ }
}
- $input_markup = sprintf(
- '
',
- $input_id,
- esc_attr( implode( ' ', $input_classes ) ),
- get_search_query(),
- esc_attr( $attributes['placeholder'] ),
- $inline_styles['input']
- );
}
if ( count( $query_params ) > 0 ) {
@@ -101,7 +116,6 @@ function render_block_core_search( $attributes ) {
if ( ! empty( $typography_classes ) ) {
$button_classes[] = $typography_classes;
}
- $aria_label = '';
if ( ! $is_button_inside && ! empty( $border_color_classes ) ) {
$button_classes[] = $border_color_classes;
@@ -111,9 +125,7 @@ function render_block_core_search( $attributes ) {
$button_internal_markup = wp_kses_post( $attributes['buttonText'] );
}
} else {
- $aria_label = sprintf( 'aria-label="%s"', esc_attr( wp_strip_all_tags( $attributes['buttonText'] ) ) );
- $button_classes[] = 'has-icon';
-
+ $button_classes[] = 'has-icon';
$button_internal_markup =
'