diff --git a/changelog.txt b/changelog.txt index cc3c407c76d77..bd7375a67e9d8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,23 @@ == Changelog == += 17.0.1 = + +## Changelog + +### Bug Fixes + +- Fix a fatal error in `WP_Fonts_Resolver::get_settings()`. ([55981](https://github.com/WordPress/gutenberg/pull/55981)) + +## Contributors + +@anton-vlasenko + + += 16.9.1 = + +Fixes a PHP fatal error in `WP_Fonts_Resolver::get_settings()`: https://github.com/WordPress/gutenberg/pull/55981 + + = 17.0.0 = diff --git a/lib/experimental/fonts-api/class-wp-fonts-resolver.php b/lib/experimental/fonts-api/class-wp-fonts-resolver.php index 144f7b30acc15..f2299c7c36831 100644 --- a/lib/experimental/fonts-api/class-wp-fonts-resolver.php +++ b/lib/experimental/fonts-api/class-wp-fonts-resolver.php @@ -200,12 +200,22 @@ private static function get_settings() { if ( $set_theme_structure ) { $set_theme_structure = false; $settings = static::set_tyopgraphy_settings_array_structure( $settings ); + + // Initialize the font families from settings if set and is an array, otherwise default to an empty array. + if ( ! isset( $settings['typography']['fontFamilies']['theme'] ) || ! is_array( $settings['typography']['fontFamilies']['theme'] ) ) { + $settings['typography']['fontFamilies']['theme'] = array(); + } } + // Initialize the font families from variation if set and is an array, otherwise default to an empty array. + $variation_font_families = ( isset( $variation['settings']['typography']['fontFamilies']['theme'] ) && is_array( $variation['settings']['typography']['fontFamilies']['theme'] ) ) + ? $variation['settings']['typography']['fontFamilies']['theme'] + : array(); + // Merge the variation settings with the global settings. $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], - $variation['settings']['typography']['fontFamilies']['theme'] + $variation_font_families ); // Make sure there are no duplicates. diff --git a/package-lock.json b/package-lock.json index 601de49933680..8b64b911fae9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.0.0", + "version": "17.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.0.0", + "version": "17.0.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -56141,7 +56141,7 @@ }, "packages/react-native-aztec": { "name": "@wordpress/react-native-aztec", - "version": "1.107.0", + "version": "1.108.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/element": "file:../element", @@ -56154,7 +56154,7 @@ }, "packages/react-native-bridge": { "name": "@wordpress/react-native-bridge", - "version": "1.107.0", + "version": "1.108.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/react-native-aztec": "file:../react-native-aztec" @@ -56165,7 +56165,7 @@ }, "packages/react-native-editor": { "name": "@wordpress/react-native-editor", - "version": "1.107.0", + "version": "1.108.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index a22544aa9b6a2..266cbcc802d14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.0.0", + "version": "17.0.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index cbe495d3787cd..536d07ec4da34 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -127,6 +127,7 @@ $z-layers: ( ".block-editor-template-part__selection-modal": 1000001, ".block-editor-block-rename-modal": 1000001, ".edit-site-list__rename-modal": 1000001, + ".dataviews-action-modal": 1000001, ".edit-site-swap-template-modal": 1000001, ".edit-site-template-panel__replace-template-modal": 1000001, diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index eef1678e2dfab..3f8a7057aef84 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -56,6 +56,14 @@ } } +.block-editor-block-contextual-toolbar.is-fixed { + position: sticky; + top: 0; + z-index: z-index(".block-editor-block-popover"); + display: block; + width: 100%; +} + // on desktop browsers the fixed toolbar has tweaked borders @include break-medium() { .block-editor-block-contextual-toolbar.is-fixed { diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index deac140f412ca..b24a25ee60ed4 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -7,22 +7,8 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - forwardRef, - useLayoutEffect, - useEffect, - useRef, - useState, -} from '@wordpress/element'; import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; -import { - ToolbarItem, - ToolbarButton, - ToolbarGroup, -} from '@wordpress/components'; -import { next, previous } from '@wordpress/icons'; -import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -32,15 +18,11 @@ import BlockToolbar from '../block-toolbar'; import { store as blockEditorStore } from '../../store'; import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls'; -function UnforwardedBlockContextualToolbar( - { focusOnMount, isFixed, ...props }, - ref -) { - // When the toolbar is fixed it can be collapsed - const [ isCollapsed, setIsCollapsed ] = useState( false ); - const toolbarButtonRef = useRef(); - - const isLargeViewport = useViewportMatch( 'medium' ); +export default function BlockContextualToolbar( { + focusOnMount, + isFixed, + ...props +} ) { const { blockType, blockEditingMode, @@ -82,92 +64,6 @@ function UnforwardedBlockContextualToolbar( }; }, [] ); - useEffect( () => { - setIsCollapsed( false ); - }, [ selectedBlockClientId ] ); - - const isLargerThanTabletViewport = useViewportMatch( 'large', '>=' ); - const isFullscreen = - document.body.classList.contains( 'is-fullscreen-mode' ); - - /** - * The following code is a workaround to fix the width of the toolbar - * it should be removed when the toolbar will be rendered inline - * FIXME: remove this layout effect when the toolbar is no longer - * absolutely positioned - */ - useLayoutEffect( () => { - // don't do anything if not fixed toolbar - if ( ! isFixed ) { - return; - } - - const blockToolbar = document.querySelector( - '.block-editor-block-contextual-toolbar' - ); - - if ( ! blockToolbar ) { - return; - } - - if ( ! blockType ) { - blockToolbar.style.width = 'initial'; - return; - } - - if ( ! isLargerThanTabletViewport ) { - // set the width of the toolbar to auto - blockToolbar.style = {}; - return; - } - - if ( isCollapsed ) { - // set the width of the toolbar to auto - blockToolbar.style.width = 'auto'; - return; - } - - // get the width of the pinned items in the post editor or widget editor - const pinnedItems = document.querySelector( - '.edit-post-header__settings, .edit-widgets-header__actions' - ); - // get the width of the left header in the site editor - const leftHeader = document.querySelector( - '.edit-site-header-edit-mode__end' - ); - - const computedToolbarStyle = window.getComputedStyle( blockToolbar ); - const computedPinnedItemsStyle = pinnedItems - ? window.getComputedStyle( pinnedItems ) - : false; - const computedLeftHeaderStyle = leftHeader - ? window.getComputedStyle( leftHeader ) - : false; - - const marginLeft = parseFloat( computedToolbarStyle.marginLeft ); - const pinnedItemsWidth = computedPinnedItemsStyle - ? parseFloat( computedPinnedItemsStyle.width ) - : 0; - const leftHeaderWidth = computedLeftHeaderStyle - ? parseFloat( computedLeftHeaderStyle.width ) - : 0; - - // set the new witdth of the toolbar - blockToolbar.style.width = `calc(100% - ${ - leftHeaderWidth + - pinnedItemsWidth + - marginLeft + - ( pinnedItems || leftHeader ? 2 : 0 ) + // Prevents button focus border from being cut off - ( isFullscreen ? 0 : 160 ) // the width of the admin sidebar expanded - }px)`; - }, [ - isFixed, - isLargerThanTabletViewport, - isCollapsed, - isFullscreen, - blockType, - ] ); - const isToolbarEnabled = blockType && hasBlockSupport( blockType, '__experimentalToolbar', true ); @@ -183,51 +79,22 @@ function UnforwardedBlockContextualToolbar( const classes = classnames( 'block-editor-block-contextual-toolbar', { 'has-parent': hasParents && showParentSelector, 'is-fixed': isFixed, - 'is-collapsed': isCollapsed, } ); return ( - { ! isCollapsed && } - { isFixed && isLargeViewport && blockType && ( - - { - setIsCollapsed( ( collapsed ) => ! collapsed ); - toolbarButtonRef.current.focus(); - } } - label={ - isCollapsed - ? __( 'Show block tools' ) - : __( 'Hide block tools' ) - } - /> - - ) } + ); } - -export const BlockContextualToolbar = forwardRef( - UnforwardedBlockContextualToolbar -); - -export default BlockContextualToolbar; diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index 696299689d185..bc2729fbb1599 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -88,8 +88,6 @@ export default function BlockTools( { moveBlocksDown, } = useDispatch( blockEditorStore ); - const selectedBlockToolsRef = useRef( null ); - function onKeyDown( event ) { if ( event.defaultPrevented ) return; @@ -132,7 +130,7 @@ export default function BlockTools( { insertBeforeBlock( clientIds[ 0 ] ); } } else if ( isMatch( 'core/block-editor/unselect', event ) ) { - if ( selectedBlockToolsRef?.current?.contains( event.target ) ) { + if ( event.target.closest( '[role=toolbar]' ) ) { // This shouldn't be necessary, but we have a combination of a few things all combining to create a situation where: // - Because the block toolbar uses createPortal to populate the block toolbar fills, we can't rely on the React event bubbling to hit the onKeyDown listener for the block toolbar // - Since we can't use the React tree, we use the DOM tree which _should_ handle the event bubbling correctly from a `createPortal` element. @@ -164,6 +162,12 @@ export default function BlockTools( { const blockToolbarRef = usePopoverScroll( __unstableContentRef ); const blockToolbarAfterRef = usePopoverScroll( __unstableContentRef ); + // Conditions for fixed toolbar + // 1. Not zoom out mode + // 2. It's a large viewport. If it's a smaller viewport, let the floating toolbar handle it as it already has styles attached to make it render that way. + // 3. Fixed toolbar is enabled + const isTopToolbar = ! isZoomOutMode && hasFixedToolbar && isLargeViewport; + return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions
@@ -173,13 +177,11 @@ export default function BlockTools( { __unstableContentRef={ __unstableContentRef } /> ) } - { ! isZoomOutMode && - ( hasFixedToolbar || ! isLargeViewport ) && ( - - ) } + { /* If there is no slot available, such as in the standalone block editor, render within the editor */ } + + { ! isLargeViewport && ( // Small viewports always get a fixed toolbar + + ) } { showEmptyBlockSideInserter && ( ) } { /* Used for the inline rich text toolbar. */ } - + { ! isTopToolbar && ( + + ) } { children } { /* Used for inline rich text popovers. */ } { shouldShowContextualToolbar && ( { initialToolbarItemIndexRef.current = index; } } - // Resets the index whenever the active block changes so - // this is not persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 - key={ clientId } /> ) } { shouldShowBreadcrumb && ( @@ -128,7 +125,3 @@ function UnforwardedSelectedBlockTools( return null; } - -export const SelectedBlockTools = forwardRef( UnforwardedSelectedBlockTools ); - -export default SelectedBlockTools; diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index 2cb2edaf1a9c7..07f22bb4946ea 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -118,16 +118,6 @@ } } - // Add a scrim to the right of the collapsed button. - &.is-collapsed::after { - content: ""; - position: absolute; - left: 100%; - width: $grid-unit-60; - height: 100%; - background: linear-gradient(to right, $white, transparent); - } - @include break-medium() { &.is-fixed { & > .block-editor-block-toolbar { diff --git a/packages/block-editor/src/components/editable-text/index.js b/packages/block-editor/src/components/editable-text/index.js index 21366087257ef..62bb166efa8e6 100644 --- a/packages/block-editor/src/components/editable-text/index.js +++ b/packages/block-editor/src/components/editable-text/index.js @@ -9,14 +9,7 @@ import { forwardRef } from '@wordpress/element'; import RichText from '../rich-text'; const EditableText = forwardRef( ( props, ref ) => { - return ( - - ); + return ; } ); EditableText.Content = ( { value = '', tagName: Tag = 'div', ...props } ) => { diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index e1204b90d0c5d..fe216e1058f6f 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -3,7 +3,6 @@ */ import { NavigableMenu, Toolbar } from '@wordpress/components'; import { - forwardRef, useState, useRef, useLayoutEffect, @@ -196,21 +195,16 @@ function useToolbarFocus( { }, [ focusEditorOnEscape, lastFocus, toolbarRef ] ); } -function UnforwardedNavigableToolbar( - { - children, - focusOnMount, - focusEditorOnEscape = false, - shouldUseKeyboardFocusShortcut = true, - __experimentalInitialIndex: initialIndex, - __experimentalOnIndexChange: onIndexChange, - ...props - }, - ref -) { - const maybeRef = useRef(); - // If a ref was not forwarded, we create one. - const toolbarRef = ref || maybeRef; +export default function NavigableToolbar( { + children, + focusOnMount, + focusEditorOnEscape = false, + shouldUseKeyboardFocusShortcut = true, + __experimentalInitialIndex: initialIndex, + __experimentalOnIndexChange: onIndexChange, + ...props +} ) { + const toolbarRef = useRef(); const isAccessibleToolbar = useIsAccessibleToolbar( toolbarRef ); useToolbarFocus( { @@ -246,7 +240,3 @@ function UnforwardedNavigableToolbar( ); } - -export const NavigableToolbar = forwardRef( UnforwardedNavigableToolbar ); - -export default NavigableToolbar; diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md index fba07d7c5d852..d17f987a34cf0 100644 --- a/packages/block-editor/src/components/rich-text/README.md +++ b/packages/block-editor/src/components/rich-text/README.md @@ -71,7 +71,8 @@ _Optional._ A list of autocompleters to use instead of the default. ### `preserveWhiteSpace: Boolean` -_Optional._ Whether or not to preserve white space characters in the `value`. Normally tab, newline and space characters are collapsed to a single space. If turned on, soft line breaks will be saved as newline characters, not as line break elements. +_Optional._ Whether or not to preserve white space characters in the `value`. Normally tab, newline and space characters are collapsed to a single space or +trimmed. ## RichText.Content diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 3fc6ec4414222..1a6793ca9efe7 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -348,7 +348,6 @@ export function RichTextWrapper( onReplace, onSplit, __unstableEmbedURLOnPaste, - preserveWhiteSpace, pastePlainText, } ), useDelete( { diff --git a/packages/block-editor/src/components/rich-text/use-paste-handler.js b/packages/block-editor/src/components/rich-text/use-paste-handler.js index 3d24906abddd9..1302e2d0dce46 100644 --- a/packages/block-editor/src/components/rich-text/use-paste-handler.js +++ b/packages/block-editor/src/components/rich-text/use-paste-handler.js @@ -35,7 +35,6 @@ export function usePasteHandler( props ) { onReplace, onSplit, __unstableEmbedURLOnPaste, - preserveWhiteSpace, pastePlainText, } = propsRef.current; @@ -63,10 +62,7 @@ export function usePasteHandler( props ) { // without filtering the data. The filters are only meant for externally // pasted content and remove inline styles. if ( isInternal ) { - const pastedValue = create( { - html, - preserveWhiteSpace, - } ); + const pastedValue = create( { html } ); addActiveFormats( pastedValue, value.activeFormats ); onChange( insert( value, pastedValue ) ); return; @@ -136,7 +132,6 @@ export function usePasteHandler( props ) { plainText, mode, tagName, - preserveWhiteSpace, } ); if ( typeof content === 'string' ) { diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index c8e81d3828392..e6113484f8a2c 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -345,6 +345,62 @@ export const withLayoutControls = createHigherOrderComponent( 'withLayoutControls' ); +function BlockWithLayoutStyles( { block: BlockListBlock, props } ) { + const { name, attributes } = props; + const id = useInstanceId( BlockListBlock ); + const { layout } = attributes; + const { default: defaultBlockLayout } = + getBlockSupport( name, layoutBlockSupportKey ) || {}; + const usedLayout = + layout?.inherit || layout?.contentSize || layout?.wideSize + ? { ...layout, type: 'constrained' } + : layout || defaultBlockLayout || {}; + const layoutClasses = useLayoutClasses( attributes, name ); + + // Higher specificity to override defaults from theme.json. + const selector = `.wp-container-${ id }.wp-container-${ id }`; + const [ blockGapSupport ] = useSettings( 'spacing.blockGap' ); + const hasBlockGapSupport = blockGapSupport !== null; + + // Get CSS string for the current layout type. + // The CSS and `style` element is only output if it is not empty. + const fullLayoutType = getLayoutType( usedLayout?.type || 'default' ); + const css = fullLayoutType?.getLayoutStyle?.( { + blockName: name, + selector, + layout: usedLayout, + style: attributes?.style, + hasBlockGapSupport, + } ); + + // Attach a `wp-container-` id-based class name as well as a layout class name such as `is-layout-flex`. + const layoutClassNames = classnames( + { + [ `wp-container-${ id }` ]: !! css, // Only attach a container class if there is generated CSS to be attached. + }, + layoutClasses + ); + + const { setStyleOverride, deleteStyleOverride } = unlock( + useDispatch( blockEditorStore ) + ); + + useEffect( () => { + if ( ! css ) return; + setStyleOverride( selector, { css } ); + return () => { + deleteStyleOverride( selector ); + }; + }, [ selector, css, setStyleOverride, deleteStyleOverride ] ); + + return ( + + ); +} + /** * Override the default block element to add the layout styles. * @@ -354,76 +410,70 @@ export const withLayoutControls = createHigherOrderComponent( */ export const withLayoutStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { - const { name, attributes } = props; - const blockSupportsLayout = hasLayoutBlockSupport( name ); - const disableLayoutStyles = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); - return !! getSettings().disableLayoutStyles; - } ); - const shouldRenderLayoutStyles = - blockSupportsLayout && ! disableLayoutStyles; - const id = useInstanceId( BlockListBlock ); - const { layout } = attributes; - const { default: defaultBlockLayout } = - getBlockSupport( name, layoutBlockSupportKey ) || {}; - const usedLayout = - layout?.inherit || layout?.contentSize || layout?.wideSize - ? { ...layout, type: 'constrained' } - : layout || defaultBlockLayout || {}; - const layoutClasses = blockSupportsLayout - ? useLayoutClasses( attributes, name ) - : null; - // Higher specificity to override defaults from theme.json. - const selector = `.wp-container-${ id }.wp-container-${ id }`; - const [ blockGapSupport ] = useSettings( 'spacing.blockGap' ); - const hasBlockGapSupport = blockGapSupport !== null; - - // Get CSS string for the current layout type. - // The CSS and `style` element is only output if it is not empty. - let css; - if ( shouldRenderLayoutStyles ) { - const fullLayoutType = getLayoutType( - usedLayout?.type || 'default' - ); - css = fullLayoutType?.getLayoutStyle?.( { - blockName: name, - selector, - layout: usedLayout, - style: attributes?.style, - hasBlockGapSupport, - } ); - } - - // Attach a `wp-container-` id-based class name as well as a layout class name such as `is-layout-flex`. - const layoutClassNames = classnames( - { - [ `wp-container-${ id }` ]: shouldRenderLayoutStyles && !! css, // Only attach a container class if there is generated CSS to be attached. + const blockSupportsLayout = hasLayoutBlockSupport( props.name ); + const shouldRenderLayoutStyles = useSelect( + ( select ) => { + // The callback returns early to avoid block editor subscription. + if ( ! blockSupportsLayout ) { + return false; + } + + return ! select( blockEditorStore ).getSettings() + .disableLayoutStyles; }, - layoutClasses + [ blockSupportsLayout ] ); - const { setStyleOverride, deleteStyleOverride } = unlock( - useDispatch( blockEditorStore ) - ); - - useEffect( () => { - if ( ! css ) return; - setStyleOverride( selector, { css } ); - return () => { - deleteStyleOverride( selector ); - }; - }, [ selector, css, setStyleOverride, deleteStyleOverride ] ); + if ( ! shouldRenderLayoutStyles ) { + return ; + } return ( - + ); }, 'withLayoutStyles' ); +function BlockWithChildLayoutStyles( { block: BlockListBlock, props } ) { + const layout = props.attributes.style?.layout ?? {}; + const { selfStretch, flexSize } = layout; + + const id = useInstanceId( BlockListBlock ); + const selector = `.wp-container-content-${ id }`; + + let css = ''; + if ( selfStretch === 'fixed' && flexSize ) { + css = `${ selector } { + flex-basis: ${ flexSize }; + box-sizing: border-box; + }`; + } else if ( selfStretch === 'fill' ) { + css = `${ selector } { + flex-grow: 1; + }`; + } + + // Attach a `wp-container-content` id-based classname. + const className = classnames( props.className, { + [ `wp-container-content-${ id }` ]: !! css, // Only attach a container class if there is generated CSS to be attached. + } ); + + const { setStyleOverride, deleteStyleOverride } = unlock( + useDispatch( blockEditorStore ) + ); + + useEffect( () => { + if ( ! css ) return; + setStyleOverride( selector, { css } ); + return () => { + deleteStyleOverride( selector ); + }; + }, [ selector, css, setStyleOverride, deleteStyleOverride ] ); + + return ; +} + /** * Override the default block element to add the child layout styles. * @@ -433,52 +483,33 @@ export const withLayoutStyles = createHigherOrderComponent( */ export const withChildLayoutStyles = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { - const { attributes } = props; - const { style: { layout = {} } = {} } = attributes; + const layout = props.attributes.style?.layout ?? {}; const { selfStretch, flexSize } = layout; const hasChildLayout = selfStretch || flexSize; - const disableLayoutStyles = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); - return !! getSettings().disableLayoutStyles; - } ); - const shouldRenderChildLayoutStyles = - hasChildLayout && ! disableLayoutStyles; - const id = useInstanceId( BlockListBlock ); - const selector = `.wp-container-content-${ id }`; + const shouldRenderChildLayoutStyles = useSelect( + ( select ) => { + // The callback returns early to avoid block editor subscription. + if ( ! hasChildLayout ) { + return false; + } - let css = ''; + return ! select( blockEditorStore ).getSettings() + .disableLayoutStyles; + }, + [ hasChildLayout ] + ); - if ( selfStretch === 'fixed' && flexSize ) { - css += `${ selector } { - flex-basis: ${ flexSize }; - box-sizing: border-box; - }`; - } else if ( selfStretch === 'fill' ) { - css += `${ selector } { - flex-grow: 1; - }`; + if ( ! shouldRenderChildLayoutStyles ) { + return ; } - // Attach a `wp-container-content` id-based classname. - const className = classnames( props?.className, { - [ `wp-container-content-${ id }` ]: - shouldRenderChildLayoutStyles && !! css, // Only attach a container class if there is generated CSS to be attached. - } ); - - const { setStyleOverride, deleteStyleOverride } = unlock( - useDispatch( blockEditorStore ) + return ( + ); - - useEffect( () => { - if ( ! css ) return; - setStyleOverride( selector, { css } ); - return () => { - deleteStyleOverride( selector ); - }; - }, [ selector, css, setStyleOverride, deleteStyleOverride ] ); - - return ; }, 'withChildLayoutStyles' ); diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index fe16d791dc7c5..cf4e251874282 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -10,6 +10,7 @@ import ResizableBoxPopover from './components/resizable-box-popover'; import { ComposedPrivateInserter as PrivateInserter } from './components/inserter'; import { PrivateListView } from './components/list-view'; import BlockInfo from './components/block-info-slot-fill'; +import BlockContextualToolbar from './components/block-tools/block-contextual-toolbar'; import { useShouldContextualToolbarShow } from './utils/use-should-contextual-toolbar-show'; import { cleanEmptyObject } from './hooks/utils'; import BlockQuickNavigation from './components/block-quick-navigation'; @@ -41,6 +42,7 @@ lock( privateApis, { PrivateListView, ResizableBoxPopover, BlockInfo, + BlockContextualToolbar, useShouldContextualToolbarShow, cleanEmptyObject, BlockQuickNavigation, diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 4f6ce7b5b044c..b21436161cb8c 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -23,7 +23,6 @@ import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import { mapRichTextSettings } from './utils'; import { retrieveSelectedAttribute, START_OF_SELECTED_AREA, @@ -759,43 +758,23 @@ export const __unstableDeleteSelection = const selectionB = selectionEnd; const blockA = select.getBlock( selectionA.clientId ); - const blockAType = getBlockType( blockA.name ); - const blockB = select.getBlock( selectionB.clientId ); - const blockBType = getBlockType( blockB.name ); const htmlA = blockA.attributes[ selectionA.attributeKey ]; const htmlB = blockB.attributes[ selectionB.attributeKey ]; - const attributeDefinitionA = - blockAType.attributes[ selectionA.attributeKey ]; - const attributeDefinitionB = - blockBType.attributes[ selectionB.attributeKey ]; - - let valueA = create( { - html: htmlA, - ...mapRichTextSettings( attributeDefinitionA ), - } ); - let valueB = create( { - html: htmlB, - ...mapRichTextSettings( attributeDefinitionB ), - } ); + let valueA = create( { html: htmlA } ); + let valueB = create( { html: htmlB } ); valueA = remove( valueA, selectionA.offset, valueA.text.length ); valueB = insert( valueB, START_OF_SELECTED_AREA, 0, selectionB.offset ); // Clone the blocks so we don't manipulate the original. const cloneA = cloneBlock( blockA, { - [ selectionA.attributeKey ]: toHTMLString( { - value: valueA, - ...mapRichTextSettings( attributeDefinitionA ), - } ), + [ selectionA.attributeKey ]: toHTMLString( { value: valueA } ), } ); const cloneB = cloneBlock( blockB, { - [ selectionB.attributeKey ]: toHTMLString( { - value: valueB, - ...mapRichTextSettings( attributeDefinitionB ), - } ), + [ selectionB.attributeKey ]: toHTMLString( { value: valueB } ), } ); const followingBlock = isForward ? cloneA : cloneB; @@ -831,20 +810,10 @@ export const __unstableDeleteSelection = const newAttributeKey = retrieveSelectedAttribute( updatedAttributes ); const convertedHtml = updatedAttributes[ newAttributeKey ]; - const convertedValue = create( { - html: convertedHtml, - ...mapRichTextSettings( - targetBlockType.attributes[ newAttributeKey ] - ), - } ); + const convertedValue = create( { html: convertedHtml } ); const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); const newValue = remove( convertedValue, newOffset, newOffset + 1 ); - const newHtml = toHTMLString( { - value: newValue, - ...mapRichTextSettings( - targetBlockType.attributes[ newAttributeKey ] - ), - } ); + const newHtml = toHTMLString( { value: newValue } ); updatedAttributes[ newAttributeKey ] = newHtml; @@ -931,27 +900,13 @@ export const __unstableSplitSelection = const selectionB = selectionEnd; const blockA = select.getBlock( selectionA.clientId ); - const blockAType = getBlockType( blockA.name ); - const blockB = select.getBlock( selectionB.clientId ); - const blockBType = getBlockType( blockB.name ); const htmlA = blockA.attributes[ selectionA.attributeKey ]; const htmlB = blockB.attributes[ selectionB.attributeKey ]; - const attributeDefinitionA = - blockAType.attributes[ selectionA.attributeKey ]; - const attributeDefinitionB = - blockBType.attributes[ selectionB.attributeKey ]; - - let valueA = create( { - html: htmlA, - ...mapRichTextSettings( attributeDefinitionA ), - } ); - let valueB = create( { - html: htmlB, - ...mapRichTextSettings( attributeDefinitionB ), - } ); + let valueA = create( { html: htmlA } ); + let valueB = create( { html: htmlB } ); valueA = remove( valueA, selectionA.offset, valueA.text.length ); valueB = remove( valueB, 0, selectionB.offset ); @@ -964,7 +919,6 @@ export const __unstableSplitSelection = ...blockA.attributes, [ selectionA.attributeKey ]: toHTMLString( { value: valueA, - ...mapRichTextSettings( attributeDefinitionA ), } ), }, }, @@ -975,7 +929,6 @@ export const __unstableSplitSelection = ...blockB.attributes, [ selectionB.attributeKey ]: toHTMLString( { value: valueB, - ...mapRichTextSettings( attributeDefinitionB ), } ), }, }, @@ -1143,10 +1096,7 @@ export const mergeBlocks = const selectedBlock = clientId === clientIdA ? cloneA : cloneB; const html = selectedBlock.attributes[ attributeKey ]; const value = insert( - create( { - html, - ...mapRichTextSettings( attributeDefinition ), - } ), + create( { html } ), START_OF_SELECTED_AREA, offset, offset @@ -1154,7 +1104,6 @@ export const mergeBlocks = selectedBlock.attributes[ attributeKey ] = toHTMLString( { value, - ...mapRichTextSettings( attributeDefinition ), } ); } @@ -1180,22 +1129,12 @@ export const mergeBlocks = const newAttributeKey = retrieveSelectedAttribute( updatedAttributes ); const convertedHtml = updatedAttributes[ newAttributeKey ]; - const convertedValue = create( { - html: convertedHtml, - ...mapRichTextSettings( - blockAType.attributes[ newAttributeKey ] - ), - } ); + const convertedValue = create( { html: convertedHtml } ); const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); const newValue = remove( convertedValue, newOffset, newOffset + 1 ); - const newHtml = toHTMLString( { - value: newValue, - ...mapRichTextSettings( - blockAType.attributes[ newAttributeKey ] - ), - } ); + const newHtml = toHTMLString( { value: newValue } ); updatedAttributes[ newAttributeKey ] = newHtml; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index e9d17d86a2672..548ad71664b5e 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -26,7 +26,6 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { mapRichTextSettings } from './utils'; import { orderBy } from '../utils/sorting'; /** @@ -1114,27 +1113,13 @@ export const __unstableGetSelectedBlocksWithPartialSelection = ( state ) => { : [ selectionAnchor, selectionFocus ]; const blockA = getBlock( state, selectionStart.clientId ); - const blockAType = getBlockType( blockA.name ); - const blockB = getBlock( state, selectionEnd.clientId ); - const blockBType = getBlockType( blockB.name ); const htmlA = blockA.attributes[ selectionStart.attributeKey ]; const htmlB = blockB.attributes[ selectionEnd.attributeKey ]; - const attributeDefinitionA = - blockAType.attributes[ selectionStart.attributeKey ]; - const attributeDefinitionB = - blockBType.attributes[ selectionEnd.attributeKey ]; - - let valueA = create( { - html: htmlA, - ...mapRichTextSettings( attributeDefinitionA ), - } ); - let valueB = create( { - html: htmlB, - ...mapRichTextSettings( attributeDefinitionB ), - } ); + let valueA = create( { html: htmlA } ); + let valueB = create( { html: htmlB } ); valueA = remove( valueA, 0, selectionStart.offset ); valueB = remove( valueB, selectionEnd.offset, valueB.text.length ); @@ -1146,7 +1131,6 @@ export const __unstableGetSelectedBlocksWithPartialSelection = ( state ) => { ...blockA.attributes, [ selectionStart.attributeKey ]: toHTMLString( { value: valueA, - ...mapRichTextSettings( attributeDefinitionA ), } ), }, }, @@ -1156,7 +1140,6 @@ export const __unstableGetSelectedBlocksWithPartialSelection = ( state ) => { ...blockB.attributes, [ selectionEnd.attributeKey ]: toHTMLString( { value: valueB, - ...mapRichTextSettings( attributeDefinitionB ), } ), }, }, diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js deleted file mode 100644 index 4a19d76d1a472..0000000000000 --- a/packages/block-editor/src/store/utils.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Helper function that maps attribute definition properties to the - * ones used by RichText utils like `create, toHTMLString, etc..`. - * - * @param {Object} attributeDefinition A block's attribute definition object. - * @return {Object} The mapped object. - */ -export function mapRichTextSettings( attributeDefinition ) { - const { __unstablePreserveWhiteSpace: preserveWhiteSpace } = - attributeDefinition; - return { preserveWhiteSpace }; -} diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index fcfb41fb0262c..af6d4686af812 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { createBlock } from '@wordpress/blocks'; +import { create, toHTMLString } from '@wordpress/rich-text'; const transforms = { from: [ @@ -12,10 +13,18 @@ const transforms = { }, { type: 'block', - blocks: [ 'core/html', 'core/paragraph' ], - transform: ( { content } ) => { + blocks: [ 'core/paragraph' ], + transform: ( { content } ) => + createBlock( 'core/code', { content } ), + }, + { + type: 'block', + blocks: [ 'core/html' ], + transform: ( { content: text } ) => { return createBlock( 'core/code', { - content, + // The HTML is plain text (with plain line breaks), so + // convert it to rich text. + content: toHTMLString( { value: create( { text } ) } ), } ); }, }, @@ -42,11 +51,8 @@ const transforms = { { type: 'block', blocks: [ 'core/paragraph' ], - transform: ( { content } ) => { - return createBlock( 'core/paragraph', { - content: content.replace( /\n/g, '
' ), - } ); - }, + transform: ( { content } ) => + createBlock( 'core/paragraph', { content } ), }, ], }; diff --git a/packages/block-library/src/html/transforms.js b/packages/block-library/src/html/transforms.js index 6265acefc8f80..af1a16288fe9f 100644 --- a/packages/block-library/src/html/transforms.js +++ b/packages/block-library/src/html/transforms.js @@ -2,15 +2,18 @@ * WordPress dependencies */ import { createBlock } from '@wordpress/blocks'; +import { create } from '@wordpress/rich-text'; const transforms = { from: [ { type: 'block', blocks: [ 'core/code' ], - transform: ( { content } ) => { + transform: ( { content: html } ) => { return createBlock( 'core/html', { - content, + // The code block may output HTML formatting, so convert it + // to plain text. + content: create( { html } ).text, } ); }, }, diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 4a902871abdee..f6936e8ecfe36 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -27,7 +27,7 @@ import { getColorClassName, useInnerBlocksProps, } from '@wordpress/block-editor'; -import { isURL, prependHTTP } from '@wordpress/url'; +import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; import { useState, useEffect, useRef } from '@wordpress/element'; import { placeCaretAtHorizontalEdge, @@ -432,7 +432,7 @@ export default function NavigationLinkEdit( { /> { updateAttributes( { url: urlValue }, diff --git a/packages/block-library/src/paragraph/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/paragraph/test/__snapshots__/transforms.native.js.snap index 22e05ce5435c9..b0855c02bd0e9 100644 --- a/packages/block-library/src/paragraph/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/paragraph/test/__snapshots__/transforms.native.js.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Paragraph block transforms to Code block 1`] = ` +" +
Example text
+" +`; + exports[`Paragraph block transforms to Columns block 1`] = ` "
diff --git a/packages/block-library/src/paragraph/test/transforms.native.js b/packages/block-library/src/paragraph/test/transforms.native.js index 0f34038ca298c..bb2d14a996625 100644 --- a/packages/block-library/src/paragraph/test/transforms.native.js +++ b/packages/block-library/src/paragraph/test/transforms.native.js @@ -23,6 +23,7 @@ const blockTransforms = [ 'Preformatted', 'Pullquote', 'Verse', + 'Code', ...transformsWithInnerBlocks, ]; diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js index 96295a641a13c..843f1cf66cdfc 100644 --- a/packages/block-library/src/post-featured-image/edit.js +++ b/packages/block-library/src/post-featured-image/edit.js @@ -43,6 +43,11 @@ function getMediaSourceUrlBySizeSlug( media, slug ) { ); } +const disabledClickProps = { + onClick: ( event ) => event.preventDefault(), + 'aria-disabled': true, +}; + export default function PostFeaturedImageEdit( { clientId, attributes, @@ -67,9 +72,10 @@ export default function PostFeaturedImageEdit( { postId ); - const { media, postType } = useSelect( + const { media, postType, postPermalink } = useSelect( ( select ) => { - const { getMedia, getPostType } = select( coreStore ); + const { getMedia, getPostType, getEditedEntityRecord } = + select( coreStore ); return { media: featuredImage && @@ -77,10 +83,16 @@ export default function PostFeaturedImageEdit( { context: 'view', } ), postType: postTypeSlug && getPostType( postTypeSlug ), + postPermalink: getEditedEntityRecord( + 'postType', + postTypeSlug, + postId + )?.link, }; }, - [ featuredImage, postTypeSlug ] + [ featuredImage, postTypeSlug, postId ] ); + const mediaUrl = getMediaSourceUrlBySizeSlug( media, sizeSlug ); const imageSizes = useSelect( @@ -197,7 +209,17 @@ export default function PostFeaturedImageEdit( { <> { controls }
- { placeholder() } + { !! isLink ? ( + + { placeholder() } + + ) : ( + placeholder() + ) } ) }
- { image } + { /* If the featured image is linked, wrap in an tag to trigger any inherited link element styles */ } + { !! isLink ? ( + + { image } + + ) : ( + image + ) } tag. + // Restore cursor style so it doesn't appear 'clickable'. + > a { + cursor: default; + } + + // When the Post Featured Image block is linked, + // and wrapped with a disabled tag + // ensure that the placeholder items are visible when selected. + &.is-selected .components-placeholder.has-illustration { + .components-button, + .components-placeholder__instructions, + .components-placeholder__label { + opacity: 1; + pointer-events: auto; + } + } } div[data-type="core/post-featured-image"] { diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json index 48804de75d2ca..d2f7c09693121 100644 --- a/packages/block-library/src/post-template/block.json +++ b/packages/block-library/src/post-template/block.json @@ -10,7 +10,6 @@ "usesContext": [ "queryId", "query", - "queryContext", "displayLayout", "templateSlug", "previewPostType", diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index f05f81c14082e..025a8bf4f6c93 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -96,7 +96,6 @@ export default function PostTemplateEdit( { // REST API or be handled by custom REST filters like `rest_{$this->post_type}_query`. ...restQueryArgs } = {}, - queryContext = [ { page: 1 } ], templateSlug, previewPostType, }, @@ -104,8 +103,6 @@ export default function PostTemplateEdit( { __unstableLayoutClassNames, } ) { const { type: layoutType, columnCount = 3 } = layout || {}; - - const [ { page } ] = queryContext; const [ activeBlockContextId, setActiveBlockContextId ] = useState(); const { posts, blocks } = useSelect( ( select ) => { @@ -126,7 +123,7 @@ export default function PostTemplateEdit( { slug: templateSlug.replace( 'category-', '' ), } ); const query = { - offset: perPage ? perPage * ( page - 1 ) + offset : 0, + offset: perPage ? perPage + offset : 0, order, orderby: orderBy, }; @@ -194,7 +191,6 @@ export default function PostTemplateEdit( { }, [ perPage, - page, offset, order, orderBy, diff --git a/packages/block-library/src/preformatted/transforms.js b/packages/block-library/src/preformatted/transforms.js index 068a08b8bcfc7..ef5f332447409 100644 --- a/packages/block-library/src/preformatted/transforms.js +++ b/packages/block-library/src/preformatted/transforms.js @@ -34,10 +34,7 @@ const transforms = { type: 'block', blocks: [ 'core/paragraph' ], transform: ( attributes ) => - createBlock( 'core/paragraph', { - ...attributes, - content: attributes.content.replace( /\n/g, '
' ), - } ), + createBlock( 'core/paragraph', attributes ), }, { type: 'block', diff --git a/packages/blocks/README.md b/packages/blocks/README.md index cbde04f72fd95..8e6fdc9d900db 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -457,7 +457,6 @@ _Parameters_ - _options.plainText_ `[string]`: Plain text version. - _options.mode_ `[string]`: Handle content as blocks or inline content. _ 'AUTO': Decide based on the content passed. _ 'INLINE': Always handle as inline content, and return string. \* 'BLOCKS': Always handle as blocks, and return array of blocks. - _options.tagName_ `[Array]`: The tag into which content will be inserted. -- _options.preserveWhiteSpace_ `[boolean]`: Whether or not to preserve consequent white space. _Returns_ diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index 9fa87462d8a1b..2f68a826931ab 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -41,12 +41,11 @@ const { console } = window; /** * Filters HTML to only contain phrasing content. * - * @param {string} HTML The HTML to filter. - * @param {boolean} preserveWhiteSpace Whether or not to preserve consequent white space. + * @param {string} HTML The HTML to filter. * * @return {string} HTML only containing phrasing content. */ -function filterInlineHTML( HTML, preserveWhiteSpace ) { +function filterInlineHTML( HTML ) { HTML = deepFilterHTML( HTML, [ headRemover, googleDocsUIDRemover, @@ -58,9 +57,7 @@ function filterInlineHTML( HTML, preserveWhiteSpace ) { inline: true, } ); - if ( ! preserveWhiteSpace ) { - HTML = deepFilterHTML( HTML, [ htmlFormattingRemover, brRemover ] ); - } + HTML = deepFilterHTML( HTML, [ htmlFormattingRemover, brRemover ] ); // Allows us to ask for this information when we get a report. console.log( 'Processed inline HTML:\n\n', HTML ); @@ -71,15 +68,14 @@ function filterInlineHTML( HTML, preserveWhiteSpace ) { /** * Converts an HTML string to known blocks. Strips everything else. * - * @param {Object} options - * @param {string} [options.HTML] The HTML to convert. - * @param {string} [options.plainText] Plain text version. - * @param {string} [options.mode] Handle content as blocks or inline content. - * * 'AUTO': Decide based on the content passed. - * * 'INLINE': Always handle as inline content, and return string. - * * 'BLOCKS': Always handle as blocks, and return array of blocks. - * @param {Array} [options.tagName] The tag into which content will be inserted. - * @param {boolean} [options.preserveWhiteSpace] Whether or not to preserve consequent white space. + * @param {Object} options + * @param {string} [options.HTML] The HTML to convert. + * @param {string} [options.plainText] Plain text version. + * @param {string} [options.mode] Handle content as blocks or inline content. + * * 'AUTO': Decide based on the content passed. + * * 'INLINE': Always handle as inline content, and return string. + * * 'BLOCKS': Always handle as blocks, and return array of blocks. + * @param {Array} [options.tagName] The tag into which content will be inserted. * * @return {Array|string} A list of blocks or a string, depending on `handlerMode`. */ @@ -88,7 +84,6 @@ export function pasteHandler( { plainText = '', mode = 'AUTO', tagName, - preserveWhiteSpace, } ) { // First of all, strip any meta tags. HTML = HTML.replace( /]+>/g, '' ); @@ -167,7 +162,7 @@ export function pasteHandler( { } if ( mode === 'INLINE' ) { - return filterInlineHTML( HTML, preserveWhiteSpace ); + return filterInlineHTML( HTML ); } if ( @@ -175,7 +170,7 @@ export function pasteHandler( { ! hasShortcodes && isInlineContent( HTML, tagName ) ) { - return filterInlineHTML( HTML, preserveWhiteSpace ); + return filterInlineHTML( HTML ); } const phrasingContentSchema = getPhrasingContentSchema( 'paste' ); diff --git a/packages/blocks/src/api/raw-handling/test/paste-handler.js b/packages/blocks/src/api/raw-handling/test/paste-handler.js index e8e1e34c7d57a..6938ad0d9c408 100644 --- a/packages/blocks/src/api/raw-handling/test/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/test/paste-handler.js @@ -69,7 +69,6 @@ describe( 'pasteHandler', () => { const [ result ] = pasteHandler( { HTML: tableWithHeaderFooterAndBodyUsingColspan, tagName: 'p', - preserveWhiteSpace: false, } ); expect( console ).toHaveLogged(); @@ -110,7 +109,6 @@ describe( 'pasteHandler', () => { const [ result ] = pasteHandler( { HTML: tableWithHeaderFooterAndBodyUsingRowspan, tagName: 'p', - preserveWhiteSpace: false, } ); expect( console ).toHaveLogged(); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c1e91d2e04720..0ca8fb69cde4f 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -13,6 +13,7 @@ - `Tabs`: Add `focusable` prop to the `Tabs.TabPanel` sub-component ([#55287](https://github.com/WordPress/gutenberg/pull/55287)) - `Tabs`: Update sub-components to accept relevant HTML element props ([#55860](https://github.com/WordPress/gutenberg/pull/55860)) - `DropdownMenuV2`: Fix radio menu item check icon not rendering correctly in some browsers ([#55964](https://github.com/WordPress/gutenberg/pull/55964)) +- `DropdownMenuV2`: prevent default when pressing Escape key to close menu ([#55962](https://github.com/WordPress/gutenberg/pull/55962)) ### Enhancements diff --git a/packages/components/src/dropdown-menu-v2-ariakit/README.md b/packages/components/src/dropdown-menu-v2-ariakit/README.md index 89fccb54e7d01..f74098efff410 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/README.md +++ b/packages/components/src/dropdown-menu-v2-ariakit/README.md @@ -113,13 +113,6 @@ The skidding of the popover along the anchor element. Can be set to negative val - Required: no - Default: `0` for root-level menus, `-8` for nested menus -##### `hideOnEscape`: `boolean | ( ( event: KeyboardEvent | React.KeyboardEvent< Element > ) => boolean )` - -Determines whether the menu popover will be hidden when the user presses the Escape key. - -- Required: no -- Default: `true` - ### `DropdownMenuItem` Used to render a menu item. diff --git a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx index d7894f565fadd..10b93d8c552c1 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx +++ b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx @@ -14,6 +14,7 @@ import { useMemo, cloneElement, isValidElement, + useCallback, } from '@wordpress/element'; import { isRTL } from '@wordpress/i18n'; import { check, chevronRightSmall } from '@wordpress/icons'; @@ -180,7 +181,6 @@ const UnconnectedDropdownMenu = ( children, shift, modal = true, - hideOnEscape = true, // From internal components context variant, @@ -248,6 +248,28 @@ const UnconnectedDropdownMenu = ( ); } + const hideOnEscape = useCallback( + ( event: React.KeyboardEvent< Element > ) => { + // Pressing Escape can cause unexpected consequences (ie. exiting + // full screen mode on MacOs, close parent modals...). + event.preventDefault(); + // Returning `true` causes the menu to hide. + return true; + }, + [] + ); + + const wrapperProps = useMemo( + () => ( { + dir: computedDirection, + style: { + direction: + computedDirection as React.CSSProperties[ 'direction' ], + }, + } ), + [ computedDirection ] + ); + return ( <> { /* Menu trigger */ } @@ -280,12 +302,7 @@ const UnconnectedDropdownMenu = ( hideOnHoverOutside={ false } data-side={ appliedPlacementSide } variant={ variant } - wrapperProps={ { - dir: computedDirection, - style: { - direction: computedDirection, - }, - } } + wrapperProps={ wrapperProps } hideOnEscape={ hideOnEscape } unmountOnHide > diff --git a/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx b/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx index 901a1c32ec474..a6319c6cfdc93 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx @@ -40,6 +40,7 @@ const meta: Meta< typeof DropdownMenu > = { }, argTypes: { children: { control: { type: null } }, + trigger: { control: { type: null } }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -494,10 +495,6 @@ export const InsideModal: StoryFn< typeof DropdownMenu > = ( props ) => { }; InsideModal.args = { ...Default.args, - hideOnEscape: ( e ) => { - e.stopPropagation(); - return true; - }, }; InsideModal.parameters = { docs: { diff --git a/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx index 3976159a82e68..297bc0dec9390 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx +++ b/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx @@ -197,32 +197,6 @@ describe( 'DropdownMenu', () => { ); } ); - it( 'should not close when pressing the escape key if the `hideOnEscape` prop is set to `false`', async () => { - render( - Open dropdown } - hideOnEscape={ false } - > - Dropdown menu item - - ); - - const trigger = screen.getByRole( 'button', { - name: 'Open dropdown', - } ); - - await click( trigger ); - - // Focuses menu on mouse click, focuses first item on keyboard press - // Can be changed with a custom useEffect - expect( screen.getByRole( 'menu' ) ).toHaveFocus(); - - // Pressing esc will close the menu and move focus to the toggle - await press.Escape(); - - expect( screen.getByRole( 'menu' ) ).toHaveFocus(); - } ); - it( 'should close when clicking outside of the content', async () => { render(