From 9d4176ca0fafcb069190d715cbdbfaa77aa9b948 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Thu, 4 Jul 2024 19:44:19 +0900 Subject: [PATCH 01/42] ToolbarButton: Always keep focusable when disabled (#63102) * ToolbarButton: Always keep focusable when disabled * Omit `__experimentalIsFocusable` from TS types * Fixup * Add changelog * Clarify deprecation comment Co-authored-by: Marco Ciampini --------- Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo Co-authored-by: tyxla Co-authored-by: jeryj Co-authored-by: t-hamano --- packages/components/CHANGELOG.md | 1 + .../components/src/toolbar/toolbar-button/index.tsx | 8 ++++++-- .../components/src/toolbar/toolbar-button/types.ts | 10 ++++++++++ packages/components/src/toolbar/toolbar-item/index.tsx | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 70f6ebd704f74..9d3903f70e2ad 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ ### Enhancements - `ToolbarButton`: Deprecate `isDisabled` prop and merge with `disabled` ([#63101](https://github.com/WordPress/gutenberg/pull/63101)). +- `ToolbarButton`: Always keep focusable when disabled ([#63102](https://github.com/WordPress/gutenberg/pull/63102)). ### Internal diff --git a/packages/components/src/toolbar/toolbar-button/index.tsx b/packages/components/src/toolbar/toolbar-button/index.tsx index 48f385fa1abaf..a8967df75690c 100644 --- a/packages/components/src/toolbar/toolbar-button/index.tsx +++ b/packages/components/src/toolbar/toolbar-button/index.tsx @@ -16,7 +16,7 @@ import Button from '../../button'; import ToolbarItem from '../toolbar-item'; import ToolbarContext from '../toolbar-context'; import ToolbarButtonContainer from './toolbar-button-container'; -import type { ToolbarButtonProps } from './types'; +import type { ToolbarButtonDeprecatedProps, ToolbarButtonProps } from './types'; import type { WordPressComponentProps } from '../../context'; function useDeprecatedProps( { @@ -30,7 +30,11 @@ function useDeprecatedProps( { } function UnforwardedToolbarButton( - props: WordPressComponentProps< ToolbarButtonProps, typeof Button, false >, + props: Omit< + WordPressComponentProps< ToolbarButtonProps, typeof Button, false >, + '__experimentalIsFocusable' // ToolbarButton will always be focusable even when disabled. + > & + ToolbarButtonDeprecatedProps, ref: ForwardedRef< any > ) { const { diff --git a/packages/components/src/toolbar/toolbar-button/types.ts b/packages/components/src/toolbar/toolbar-button/types.ts index bcd349c0d8936..59965e66d1347 100644 --- a/packages/components/src/toolbar/toolbar-button/types.ts +++ b/packages/components/src/toolbar/toolbar-button/types.ts @@ -37,6 +37,16 @@ export type ToolbarButtonProps = { title?: string; }; +export type ToolbarButtonDeprecatedProps = { + /** + * Whether to keep the button focusable when disabled. + * + * @deprecated ToolbarButton will always be focusable even when disabled. + * @ignore + */ + __experimentalIsFocusable?: boolean; +}; + export type ToolbarButtonContainerProps = { /** * Children to be rendered inside the button container. diff --git a/packages/components/src/toolbar/toolbar-item/index.tsx b/packages/components/src/toolbar/toolbar-item/index.tsx index 2c343eb173a27..7f4ccb7f659cd 100644 --- a/packages/components/src/toolbar/toolbar-item/index.tsx +++ b/packages/components/src/toolbar/toolbar-item/index.tsx @@ -50,6 +50,7 @@ function ToolbarItem( return ( Date: Thu, 4 Jul 2024 12:50:45 +0200 Subject: [PATCH 02/42] Update: Move template areas into a panel. (#62033) Co-authored-by: jorgefilipecosta Co-authored-by: jameskoster Co-authored-by: jasmussen --- .../block-quick-navigation/index.js | 49 +++++----- .../editor/src/components/sidebar/index.js | 4 +- .../src/components/sidebar/post-summary.js | 2 - .../src/components/template-areas/index.js | 94 ------------------- .../src/components/template-areas/style.scss | 22 ----- .../template-content-panel/index.js | 34 ++++++- .../editor/src/store/private-selectors.js | 25 ----- packages/editor/src/style.scss | 1 - 8 files changed, 51 insertions(+), 180 deletions(-) delete mode 100644 packages/editor/src/components/template-areas/index.js delete mode 100644 packages/editor/src/components/template-areas/style.scss diff --git a/packages/block-editor/src/components/block-quick-navigation/index.js b/packages/block-editor/src/components/block-quick-navigation/index.js index 7a0e7984b83cb..63d0edbb868aa 100644 --- a/packages/block-editor/src/components/block-quick-navigation/index.js +++ b/packages/block-editor/src/components/block-quick-navigation/index.js @@ -10,18 +10,16 @@ import { FlexBlock, FlexItem, } from '@wordpress/components'; -import { - __experimentalGetBlockLabel, - store as blocksStore, -} from '@wordpress/blocks'; /** * Internal dependencies */ import { store as blockEditorStore } from '../../store'; import BlockIcon from '../block-icon'; +import useBlockDisplayInformation from '../use-block-display-information'; +import useBlockDisplayTitle from '../block-title/use-block-display-title'; -export default function BlockQuickNavigation( { clientIds } ) { +export default function BlockQuickNavigation( { clientIds, onSelect } ) { if ( ! clientIds.length ) { return null; } @@ -29,6 +27,7 @@ export default function BlockQuickNavigation( { clientIds } ) { { clientIds.map( ( clientId ) => ( @@ -37,29 +36,18 @@ export default function BlockQuickNavigation( { clientIds } ) { ); } -function BlockQuickNavigationItem( { clientId } ) { - const { name, icon, isSelected } = useSelect( +function BlockQuickNavigationItem( { clientId, onSelect } ) { + const blockInformation = useBlockDisplayInformation( clientId ); + const blockTitle = useBlockDisplayTitle( { + clientId, + context: 'list-view', + } ); + const { isSelected } = useSelect( ( select ) => { - const { - getBlockName, - getBlockAttributes, - isBlockSelected, - hasSelectedInnerBlock, - } = select( blockEditorStore ); - const { getBlockType } = select( blocksStore ); - - const blockType = getBlockType( getBlockName( clientId ) ); - const attributes = getBlockAttributes( clientId ); + const { isBlockSelected, hasSelectedInnerBlock } = + select( blockEditorStore ); return { - name: - blockType && - __experimentalGetBlockLabel( - blockType, - attributes, - 'list-view' - ), - icon: blockType?.icon, isSelected: isBlockSelected( clientId ) || hasSelectedInnerBlock( clientId, /* deep: */ true ), @@ -72,14 +60,19 @@ function BlockQuickNavigationItem( { clientId } ) { return ( diff --git a/packages/editor/src/components/sidebar/index.js b/packages/editor/src/components/sidebar/index.js index 668f8bc7b8517..dbd33459b7f0f 100644 --- a/packages/editor/src/components/sidebar/index.js +++ b/packages/editor/src/components/sidebar/index.js @@ -112,9 +112,7 @@ const SidebarContent = ( { - { renderingMode !== 'post-only' && ( - - ) } + diff --git a/packages/editor/src/components/sidebar/post-summary.js b/packages/editor/src/components/sidebar/post-summary.js index 676c330e708db..a6a95d36388ba 100644 --- a/packages/editor/src/components/sidebar/post-summary.js +++ b/packages/editor/src/components/sidebar/post-summary.js @@ -28,7 +28,6 @@ import BlogTitle from '../blog-title'; import PostsPerPage from '../posts-per-page'; import SiteDiscussion from '../site-discussion'; import { store as editorStore } from '../../store'; -import TemplateAreas from '../template-areas'; import { PrivatePostLastRevision } from '../post-last-revision'; /** @@ -84,7 +83,6 @@ export default function PostSummary( { onActionPerformed } ) { - { fills } ) } diff --git a/packages/editor/src/components/template-areas/index.js b/packages/editor/src/components/template-areas/index.js deleted file mode 100644 index 0d3bbe42d02ee..0000000000000 --- a/packages/editor/src/components/template-areas/index.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { - Button, - __experimentalHeading as Heading, -} from '@wordpress/components'; - -import { store as blockEditorStore } from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { store as editorStore } from '../../store'; -import { unlock } from '../../lock-unlock'; -import { TEMPLATE_POST_TYPE } from '../../store/constants'; - -function TemplateAreaItem( { area, clientId } ) { - const { selectBlock, toggleBlockHighlight } = - useDispatch( blockEditorStore ); - const templatePartArea = useSelect( - ( select ) => { - const defaultAreas = - select( - editorStore - ).__experimentalGetDefaultTemplatePartAreas(); - - return defaultAreas.find( - ( defaultArea ) => defaultArea.area === area - ); - }, - [ area ] - ); - - const highlightBlock = () => toggleBlockHighlight( clientId, true ); - const cancelHighlightBlock = () => toggleBlockHighlight( clientId, false ); - - return ( - - ); -} - -export default function TemplateAreas() { - const { isTemplate, templateParts } = useSelect( ( select ) => { - const _isTemplate = - select( editorStore ).getCurrentPostType() === TEMPLATE_POST_TYPE; - - return { - isTemplate: _isTemplate, - templateParts: - _isTemplate && - unlock( - select( editorStore ) - ).getCurrentTemplateTemplateParts(), - }; - }, [] ); - - if ( ! isTemplate || ! templateParts.length ) { - return null; - } - - return ( -
- - { __( 'Areas' ) } - - -
    - { templateParts.map( ( { templatePart, block } ) => ( -
  • - -
  • - ) ) } -
-
- ); -} diff --git a/packages/editor/src/components/template-areas/style.scss b/packages/editor/src/components/template-areas/style.scss deleted file mode 100644 index 4a0dcd748c2ed..0000000000000 --- a/packages/editor/src/components/template-areas/style.scss +++ /dev/null @@ -1,22 +0,0 @@ -.editor-template-areas { - &__list { - margin: 0; - > li { - margin: 0; - } - } - - &__item { - width: 100%; - - // Override the default padding. - &.components-button.has-icon { - padding: 0; - } - } -} - -h3.components-heading.editor-template-areas__title { - font-weight: 500; - margin: 0 0 $grid-unit-10; -} diff --git a/packages/editor/src/components/template-content-panel/index.js b/packages/editor/src/components/template-content-panel/index.js index e0f131f27feb5..14532387ecd05 100644 --- a/packages/editor/src/components/template-content-panel/index.js +++ b/packages/editor/src/components/template-content-panel/index.js @@ -1,18 +1,21 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { store as blockEditorStore, privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { PanelBody } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { store as interfaceStore } from '@wordpress/interface'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { TEMPLATE_POST_TYPE } from '../../store/constants'; +import { store as editorStore } from '../../store'; const { BlockQuickNavigation } = unlock( blockEditorPrivateApis ); @@ -22,15 +25,36 @@ const PAGE_CONTENT_BLOCKS = [ 'core/post-title', ]; -export default function TemplateContentPanel() { - const clientIds = useSelect( ( select ) => { +const TEMPLATE_PART_BLOCK = 'core/template-part'; + +export default function TemplateContentPanel( { renderingMode } ) { + const { enableComplementaryArea } = useDispatch( interfaceStore ); + const { clientIds, postType } = useSelect( ( select ) => { const { getBlocksByName } = select( blockEditorStore ); - return getBlocksByName( PAGE_CONTENT_BLOCKS ); + const { getCurrentPostType } = select( editorStore ); + const _postType = getCurrentPostType(); + return { + postType: _postType, + clientIds: getBlocksByName( + TEMPLATE_POST_TYPE === _postType + ? TEMPLATE_PART_BLOCK + : PAGE_CONTENT_BLOCKS + ), + }; }, [] ); + if ( renderingMode === 'post-only' && postType !== TEMPLATE_POST_TYPE ) { + return null; + } + return ( - + { + enableComplementaryArea( 'core', 'edit-post/document' ); + } } + /> ); } diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js index f56e1e8c9e81f..c635cbc816f92 100644 --- a/packages/editor/src/store/private-selectors.js +++ b/packages/editor/src/store/private-selectors.js @@ -25,8 +25,6 @@ import { getCurrentPost, __experimentalGetDefaultTemplatePartAreas, } from './selectors'; -import { TEMPLATE_PART_POST_TYPE } from './constants'; -import { getFilteredTemplatePartBlocks } from './utils/get-filtered-template-parts'; import { getEntityActions as _getEntityActions } from '../dataviews/store/private-selectors'; const EMPTY_INSERTION_POINT = { @@ -120,29 +118,6 @@ export const getPostIcon = createRegistrySelector( } ); -/** - * Returns the template parts and their blocks for the current edited template. - * - * @param {Object} state Global application state. - * @return {Array} Template parts and their blocks in an array. - */ -export const getCurrentTemplateTemplateParts = createRegistrySelector( - ( select ) => () => { - const templateParts = select( coreStore ).getEntityRecords( - 'postType', - TEMPLATE_PART_POST_TYPE, - { per_page: -1 } - ); - - const clientIds = - select( blockEditorStore ).getBlocksByName( 'core/template-part' ); - const blocks = - select( blockEditorStore ).getBlocksByClientId( clientIds ); - - return getFilteredTemplatePartBlocks( blocks, templateParts ); - } -); - /** * Returns true if there are unsaved changes to the * post's meta fields, and false otherwise. diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index a4034a8a51c73..d1af3095230f3 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -54,6 +54,5 @@ @import "./components/sidebar/style.scss"; @import "./components/site-discussion/style.scss"; @import "./components/table-of-contents/style.scss"; -@import "./components/template-areas/style.scss"; @import "./components/text-editor/style.scss"; @import "./components/visual-editor/style.scss"; From bd8a9376e10f485d640b13ab84b29dbae6cf6f70 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:06:29 +0900 Subject: [PATCH 03/42] Table Block: Add toolbar button to add a caption (#47984) * Use Caption component * Fix e2e test Co-authored-by: t-hamano Co-authored-by: kevin940726 Co-authored-by: afercia Co-authored-by: Mamaduka Co-authored-by: BenjaminZekavica --- packages/block-library/src/table/edit.js | 35 +++++++--------------- test/e2e/specs/editor/blocks/table.spec.js | 9 +++--- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index f0c3ece790863..5510d0031ff6b 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -16,7 +16,6 @@ import { useBlockProps, __experimentalUseColorProps as useColorProps, __experimentalUseBorderProps as useBorderProps, - __experimentalGetElementClassName, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { @@ -41,7 +40,6 @@ import { tableRowDelete, table, } from '@wordpress/icons'; -import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; /** * Internal dependencies @@ -57,6 +55,7 @@ import { toggleSection, isEmptyTableSection, } from './state'; +import { Caption } from '../utils/caption'; const ALIGNMENT_CONTROLS = [ { @@ -98,7 +97,7 @@ function TableEdit( { insertBlocksAfter, isSelected, } ) { - const { hasFixedLayout, caption, head, foot } = attributes; + const { hasFixedLayout, head, foot } = attributes; const [ initialRowCount, setInitialRowCount ] = useState( 2 ); const [ initialColumnCount, setInitialColumnCount ] = useState( 2 ); const [ selectedCell, setSelectedCell ] = useState(); @@ -523,27 +522,7 @@ function TableEdit( { { renderedSections } ) } - { ! isEmpty && ( - - setAttributes( { caption: value } ) - } - // Deselect the selected table cell when the caption is focused. - onFocus={ () => setSelectedCell() } - __unstableOnSplitAtEnd={ () => - insertBlocksAfter( - createBlock( getDefaultBlockName() ) - ) - } - /> - ) } - { isEmpty && ( + { isEmpty ? ( } @@ -582,6 +561,14 @@ function TableEdit( { + ) : ( + ) } ); diff --git a/test/e2e/specs/editor/blocks/table.spec.js b/test/e2e/specs/editor/blocks/table.spec.js index 1e6dfdcd76e18..92e209e88aeb1 100644 --- a/test/e2e/specs/editor/blocks/table.spec.js +++ b/test/e2e/specs/editor/blocks/table.spec.js @@ -275,10 +275,11 @@ test.describe( 'Table', () => { .locator( 'role=button[name="Create Table"i]' ) .click(); - // Click the first cell and add some text. - await editor.canvas - .locator( 'role=document[name="Block: Table"i] >> figcaption' ) - .click(); + await editor.clickBlockToolbarButton( 'Add caption' ); + const caption = editor.canvas.locator( + 'role=textbox[name="Table caption text"i]' + ); + await expect( caption ).toBeFocused(); await page.keyboard.type( 'Caption!' ); expect( await editor.getEditedPostContent() ).toMatchSnapshot(); } ); From d51d7e5327efd8cf7d0fbe87594df9f7addd8cab Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 4 Jul 2024 13:25:50 +0200 Subject: [PATCH 04/42] Fix: Permanently delete post action does not calls onActionPerformed. (#63121) Co-authored-by: jorgefilipecosta --- packages/editor/src/components/post-actions/actions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 2f97d0aa9d0a8..fa6456a05e2c7 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -276,7 +276,7 @@ const permanentlyDeletePostAction = { isEligible( { status } ) { return status === 'trash'; }, - async callback( posts, { registry } ) { + async callback( posts, { registry, onActionPerformed } ) { const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore ); const { deleteEntityRecord } = registry.dispatch( coreStore ); @@ -307,6 +307,7 @@ const permanentlyDeletePostAction = { type: 'snackbar', id: 'permanently-delete-post-action', } ); + onActionPerformed?.( posts ); } else { // If there was at lease one failure. let errorMessage; From 2c62c6aa2f30060b3acd40b225aebe4295ad0bf4 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Thu, 4 Jul 2024 21:40:36 +0900 Subject: [PATCH 05/42] Button: Stabilize `__experimentalIsFocusable` prop (#62282) * Button: Stabilize `__experimentalIsFocusable` prop * Add changelog * Tweak changelog Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> * Replace `__experimentalIsFocusable` with `accessibleWhenDisabled` (#63107) * Replace `__experimentalIsFocusable` with `accessibleWhenDisabled` * Replace a few more remaining instances * Revert "Replace a few more remaining instances" This reverts commit 406ef77ae09b16060fbba9e86840c6d53501393c. --------- Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> * Fix new violation --------- Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: tyxla Co-authored-by: stokesman --- .eslintrc.js | 4 +- .../downloadable-block-list-item/index.js | 2 +- .../get-install-missing/install-button.js | 2 +- .../src/components/block-mover/button.js | 2 +- .../block-pattern-setup/setup-toolbar.js | 4 +- .../components/block-patterns-paging/index.js | 8 ++-- .../components/link-control/link-preview.js | 2 +- .../src/page-list/convert-to-links-modal.js | 2 +- packages/block-library/src/page-list/edit.js | 2 +- .../src/template-part/edit/title-modal.js | 2 +- packages/components/CHANGELOG.md | 1 + .../src/autocomplete/autocompleter-ui.tsx | 2 +- packages/components/src/button/README.md | 13 +++++++ packages/components/src/button/index.tsx | 10 +++-- .../src/button/stories/e2e/index.story.tsx | 2 +- packages/components/src/button/test/index.tsx | 13 ++++++- packages/components/src/button/types.ts | 37 ++++++++++++++----- .../components/src/dropdown-menu/index.tsx | 2 +- .../components/src/font-size-picker/index.tsx | 2 +- .../components/src/range-control/index.tsx | 2 +- .../src/toolbar/toolbar-button/index.tsx | 2 +- packages/dataviews/src/add-filter.tsx | 2 +- packages/dataviews/src/item-actions.tsx | 2 +- packages/dataviews/src/pagination.tsx | 4 +- packages/dataviews/src/reset-filters.tsx | 2 +- packages/dataviews/src/view-list.tsx | 2 +- .../components/init-pattern-modal/index.js | 2 +- .../preferences-modal/enable-custom-fields.js | 2 +- .../components/global-styles-sidebar/index.js | 4 +- .../font-library-modal/font-collection.js | 10 ++--- .../font-library-modal/installed-fonts.js | 2 +- .../screen-revisions/revisions-buttons.js | 2 +- .../src/components/pagination/index.js | 8 ++-- .../src/components/save-panel/index.js | 2 +- .../rename-modal.js | 2 +- .../push-changes-to-global-styles/index.js | 2 +- .../components/entities-saved-states/index.js | 2 +- .../src/components/post-actions/actions.js | 4 +- .../src/components/post-actions/index.js | 2 +- .../components/post-preview-button/index.js | 2 +- .../components/post-publish-panel/index.js | 2 +- .../src/components/preview-dropdown/index.js | 2 +- .../components/save-publish-panels/index.js | 2 +- .../src/dataviews/actions/delete-post.tsx | 4 +- .../src/dataviews/actions/reset-post.tsx | 4 +- .../src/components/import-form/index.js | 2 +- .../components/pattern-overrides-controls.js | 2 +- .../playground/with-undo-redo/index.js | 4 +- 48 files changed, 119 insertions(+), 77 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 7d1bb161a7c3f..a3ecf750dbb12 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -143,9 +143,9 @@ const restrictedSyntax = [ const restrictedSyntaxComponents = [ { selector: - 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]', + 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="accessibleWhenDisabled"])) JSXAttribute[name.name="disabled"]', message: - '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)', + '`disabled` used without the `accessibleWhenDisabled` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)', }, ]; diff --git a/packages/block-directory/src/components/downloadable-block-list-item/index.js b/packages/block-directory/src/components/downloadable-block-list-item/index.js index e3a770925551c..b1ef34ccdba59 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/index.js +++ b/packages/block-directory/src/components/downloadable-block-list-item/index.js @@ -96,7 +96,7 @@ function DownloadableBlockListItem( { composite, item, onClick } ) { { getBlockMoverDescription( diff --git a/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js b/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js index 91b68456cda71..df559194e5b78 100644 --- a/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js +++ b/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js @@ -35,14 +35,14 @@ const CarouselNavigation = ( { label={ __( 'Previous pattern' ) } onClick={ handlePrevious } disabled={ activeSlide === 0 } - __experimentalIsFocusable + accessibleWhenDisabled /> @@ -51,7 +51,7 @@ export default function Pagination( { onClick={ () => changePage( currentPage - 1 ) } disabled={ currentPage === 1 } aria-label={ __( 'Previous page' ) } - __experimentalIsFocusable + accessibleWhenDisabled > @@ -74,7 +74,7 @@ export default function Pagination( { onClick={ () => changePage( currentPage + 1 ) } disabled={ currentPage === numPages } aria-label={ __( 'Next page' ) } - __experimentalIsFocusable + accessibleWhenDisabled > @@ -84,7 +84,7 @@ export default function Pagination( { disabled={ currentPage === numPages } aria-label={ __( 'Last page' ) } size="default" - __experimentalIsFocusable + accessibleWhenDisabled > » diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js index d06c9971c680b..e5de47f0153a8 100644 --- a/packages/block-editor/src/components/link-control/link-preview.js +++ b/packages/block-editor/src/components/link-control/link-preview.js @@ -166,7 +166,7 @@ export default function LinkPreview( { isEmptyURL || showIconLabels ? '' : ': ' + value.url ) } ref={ ref } - __experimentalIsFocusable + accessibleWhenDisabled disabled={ isEmptyURL } size="compact" /> diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js index 12277bee01faf..209987b194238 100644 --- a/packages/block-library/src/page-list/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/convert-to-links-modal.js @@ -36,7 +36,7 @@ export function ConvertToLinksModal( { onClick, onClose, disabled } ) { diff --git a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js index e655a7300c37e..94d4f28c632cd 100644 --- a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js +++ b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js @@ -42,7 +42,7 @@ export function CustomFieldsConfirmation( { willEnable } ) { className="edit-post-preferences-modal__custom-fields-confirmation-button" variant="secondary" isBusy={ isReloading } - __experimentalIsFocusable + accessibleWhenDisabled disabled={ isReloading } onClick={ () => { setIsReloading( true ); diff --git a/packages/edit-site/src/components/global-styles-sidebar/index.js b/packages/edit-site/src/components/global-styles-sidebar/index.js index f57cc8c417f41..b314b5d7e7524 100644 --- a/packages/edit-site/src/components/global-styles-sidebar/index.js +++ b/packages/edit-site/src/components/global-styles-sidebar/index.js @@ -152,7 +152,7 @@ export default function GlobalStylesSidebar() { isPressed={ isStyleBookOpened || isRevisionsStyleBookOpened } - __experimentalIsFocusable + accessibleWhenDisabled disabled={ shouldClearCanvasContainerView } onClick={ toggleStyleBook } size="compact" @@ -163,7 +163,7 @@ export default function GlobalStylesSidebar() { label={ __( 'Revisions' ) } icon={ backup } onClick={ toggleRevisions } - __experimentalIsFocusable + accessibleWhenDisabled disabled={ ! hasRevisions } isPressed={ isRevisionsOpened || isRevisionsStyleBookOpened diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index db9e3cb402561..9cb446b1d16ab 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -438,7 +438,7 @@ function FontCollection( { slug } ) { disabled={ fontsToInstall.length === 0 || isInstalling } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Install' ) } @@ -455,7 +455,7 @@ function FontCollection( { slug } ) { size="compact" onClick={ () => setPage( 1 ) } disabled={ page === 1 } - __experimentalIsFocusable + accessibleWhenDisabled > « @@ -464,7 +464,7 @@ function FontCollection( { slug } ) { size="compact" onClick={ () => setPage( page - 1 ) } disabled={ page === 1 } - __experimentalIsFocusable + accessibleWhenDisabled > @@ -514,7 +514,7 @@ function FontCollection( { slug } ) { size="compact" onClick={ () => setPage( page + 1 ) } disabled={ page === totalPages } - __experimentalIsFocusable + accessibleWhenDisabled > @@ -523,7 +523,7 @@ function FontCollection( { slug } ) { size="compact" onClick={ () => setPage( totalPages ) } disabled={ page === totalPages } - __experimentalIsFocusable + accessibleWhenDisabled > » diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js index c644c219348a6..464540bc2b4ea 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js @@ -329,7 +329,7 @@ function InstalledFonts() { saveFontFamilies( fontFamilies ); } } disabled={ ! fontFamiliesHasChanges } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Update' ) } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index 7f4f9896344b4..a17e5fde5763a 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -163,7 +163,7 @@ function RevisionsButtons( { > diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js index f704f2354e439..d1a2df9df6cdc 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js @@ -49,7 +49,7 @@ export default function RenameModal( { menuTitle, onClose, onSave } ) { @@ -233,7 +233,7 @@ const trashPostAction = { } } isBusy={ isBusy } disabled={ isBusy } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Trash' ) } diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index 00d256ef67eee..b2730e760c427 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -59,7 +59,7 @@ export default function PostActions( { onActionPerformed, buttonProps } ) { icon={ moreVertical } label={ __( 'Actions' ) } disabled={ ! actions.length } - __experimentalIsFocusable + accessibleWhenDisabled className="editor-all-actions-button" onClick={ () => setIsActionsMenuOpen( ! isActionsMenuOpen ) diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index e517ac0e8a0fb..d57143cd355d8 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -183,7 +183,7 @@ export default function PostPreviewButton( { className={ className || 'editor-post-preview' } href={ href } target={ targetId } - __experimentalIsFocusable + accessibleWhenDisabled disabled={ ! isSaveable } onClick={ openPreviewWindow } role={ role } diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js index 009d008c72c96..28567c6078021 100644 --- a/packages/editor/src/components/post-publish-panel/index.js +++ b/packages/editor/src/components/post-publish-panel/index.js @@ -93,7 +93,7 @@ export class PostPublishPanel extends Component {
diff --git a/packages/editor/src/dataviews/actions/delete-post.tsx b/packages/editor/src/dataviews/actions/delete-post.tsx index e5810e32fde69..36400702dae3f 100644 --- a/packages/editor/src/dataviews/actions/delete-post.tsx +++ b/packages/editor/src/dataviews/actions/delete-post.tsx @@ -77,7 +77,7 @@ const deletePostAction: Action< Post > = { variant="tertiary" onClick={ closeModal } disabled={ isBusy } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Cancel' ) } @@ -94,7 +94,7 @@ const deletePostAction: Action< Post > = { } } isBusy={ isBusy } disabled={ isBusy } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Delete' ) } diff --git a/packages/editor/src/dataviews/actions/reset-post.tsx b/packages/editor/src/dataviews/actions/reset-post.tsx index 56440e6da3383..17c2860444630 100644 --- a/packages/editor/src/dataviews/actions/reset-post.tsx +++ b/packages/editor/src/dataviews/actions/reset-post.tsx @@ -116,7 +116,7 @@ const resetPost: Action< Post > = { variant="tertiary" onClick={ closeModal } disabled={ isBusy } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Cancel' ) } @@ -131,7 +131,7 @@ const resetPost: Action< Post > = { } } isBusy={ isBusy } disabled={ isBusy } - __experimentalIsFocusable + accessibleWhenDisabled > { __( 'Reset' ) } diff --git a/packages/list-reusable-blocks/src/components/import-form/index.js b/packages/list-reusable-blocks/src/components/import-form/index.js index aa2f3133b7a6e..9c6e8bf57835f 100644 --- a/packages/list-reusable-blocks/src/components/import-form/index.js +++ b/packages/list-reusable-blocks/src/components/import-form/index.js @@ -86,7 +86,7 @@ function ImportForm( { instanceId, onUpload } ) { +
+ + +
+ +
+ + ) } @@ -300,23 +301,20 @@ export default function DataviewsPatterns() { header: __( 'Sync status' ), id: 'sync-status', render: ( { item } ) => { + const syncStatus = + 'wp_pattern_sync_status' in item + ? item.wp_pattern_sync_status || + PATTERN_SYNC_TYPES.full + : PATTERN_SYNC_TYPES.unsynced; // User patterns can have their sync statuses checked directly. // Non-user patterns are all unsynced for the time being. return ( { - ( - SYNC_FILTERS.find( - ( { value } ) => - value === item.syncStatus - ) || - SYNC_FILTERS.find( - ( { value } ) => - value === - PATTERN_SYNC_TYPES.unsynced - ) + SYNC_FILTERS.find( + ( { value } ) => value === syncStatus ).label } diff --git a/packages/edit-site/src/components/page-patterns/search-items.js b/packages/edit-site/src/components/page-patterns/search-items.js index 64565d951e833..1d03400c86c8c 100644 --- a/packages/edit-site/src/components/page-patterns/search-items.js +++ b/packages/edit-site/src/components/page-patterns/search-items.js @@ -24,11 +24,42 @@ import { } from '../../utils/constants'; // Default search helpers. -const defaultGetName = ( item ) => - item.type !== TEMPLATE_PART_POST_TYPE ? item.name || '' : ''; -export const defaultGetTitle = ( item ) => - typeof item.title === 'string' ? item.title : item.title.rendered; -const defaultGetDescription = ( item ) => item.description || ''; +const defaultGetName = ( item ) => { + if ( item.type === PATTERN_TYPES.user ) { + return item.slug; + } + + if ( item.type === TEMPLATE_PART_POST_TYPE ) { + return ''; + } + + return item.name || ''; +}; + +export const defaultGetTitle = ( item ) => { + if ( typeof item.title === 'string' ) { + return item.title; + } + + if ( item.title && item.title.rendered ) { + return item.title.rendered; + } + + if ( item.title && item.title.raw ) { + return item.title.raw; + } + + return ''; +}; + +const defaultGetDescription = ( item ) => { + if ( item.type === PATTERN_TYPES.user ) { + return item.excerpt.raw; + } + + return item.description || ''; +}; + const defaultGetKeywords = ( item ) => item.keywords || []; const defaultHasCategory = () => false; diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index dc5a92b560537..ecbc6adee7558 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -128,8 +128,11 @@ const selectPatterns = createSelector( patterns: themePatterns, isResolving: isResolvingThemePatterns, } = selectThemePatterns( select ); - const { patterns: userPatterns, isResolving: isResolvingUserPatterns } = - selectUserPatterns( select ); + const { + patterns: userPatterns, + isResolving: isResolvingUserPatterns, + categories: userPatternCategories, + } = selectUserPatterns( select ); let patterns = [ ...( themePatterns || [] ), @@ -141,7 +144,8 @@ const selectPatterns = createSelector( // Non-user patterns are all unsynced for the time being. patterns = patterns.filter( ( pattern ) => { return pattern.type === PATTERN_TYPES.user - ? pattern.syncStatus === syncStatus + ? ( pattern.wp_pattern_sync_status || + PATTERN_SYNC_TYPES.full ) === syncStatus : syncStatus === PATTERN_SYNC_TYPES.unsynced; } ); } @@ -149,12 +153,35 @@ const selectPatterns = createSelector( if ( categoryId ) { patterns = searchItems( patterns, search, { categoryId, - hasCategory: ( item, currentCategory ) => - item.categories?.includes( currentCategory ), + hasCategory: ( item, currentCategory ) => { + if ( item.type === PATTERN_TYPES.user ) { + return item.wp_pattern_category.some( + ( catId ) => + userPatternCategories.find( + ( cat ) => cat.id === catId + )?.slug === currentCategory + ); + } + return item.categories?.includes( currentCategory ); + }, } ); } else { patterns = searchItems( patterns, search, { - hasCategory: ( item ) => ! item.hasOwnProperty( 'categories' ), + hasCategory: ( item ) => { + if ( item.type === PATTERN_TYPES.user ) { + return ( + userPatternCategories?.length && + ( ! item.wp_pattern_category?.length || + ! item.wp_pattern_category.some( ( catId ) => + userPatternCategories.find( + ( cat ) => cat.id === catId + ) + ) ) + ); + } + + return ! item.hasOwnProperty( 'categories' ); + }, } ); } return { @@ -168,41 +195,6 @@ const selectPatterns = createSelector( ] ); -/** - * Converts a post of type `wp_block` to a 'pattern item' that more closely - * matches the structure of theme provided patterns. - * - * @param {Object} patternPost The `wp_block` record being normalized. - * @param {Map} categories A Map of user created categories. - * - * @return {Object} The normalized item. - */ -const convertPatternPostToItem = ( patternPost, categories ) => ( { - blocks: parse( patternPost.content.raw, { - __unstableSkipMigrationLogs: true, - } ), - ...( patternPost.wp_pattern_category.length > 0 && { - categories: patternPost.wp_pattern_category.map( - ( patternCategoryId ) => - categories && categories.get( patternCategoryId ) - ? categories.get( patternCategoryId ).slug - : patternCategoryId - ), - } ), - termLabels: patternPost.wp_pattern_category.map( ( patternCategoryId ) => - categories?.get( patternCategoryId ) - ? categories.get( patternCategoryId ).label - : patternCategoryId - ), - id: patternPost.id, - name: patternPost.slug, - syncStatus: patternPost.wp_pattern_sync_status || PATTERN_SYNC_TYPES.full, - title: patternPost.title.raw, - type: patternPost.type, - description: patternPost.excerpt.raw, - patternPost, -} ); - const selectUserPatterns = createSelector( ( select, syncStatus, search = '' ) => { const { @@ -222,12 +214,7 @@ const selectUserPatterns = createSelector( userPatternCategories.forEach( ( userCategory ) => categories.set( userCategory.id, userCategory ) ); - let patterns = patternPosts - ? patternPosts.map( ( record ) => - convertPatternPostToItem( record, categories ) - ) - : EMPTY_PATTERN_LIST; - + let patterns = patternPosts ?? EMPTY_PATTERN_LIST; const isResolving = isResolvingSelector( 'getEntityRecords', [ 'postType', PATTERN_TYPES.user, @@ -236,7 +223,9 @@ const selectUserPatterns = createSelector( if ( syncStatus ) { patterns = patterns.filter( - ( pattern ) => pattern.syncStatus === syncStatus + ( pattern ) => + pattern.wp_pattern_sync_status || + PATTERN_SYNC_TYPES.full === syncStatus ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js index 4d80d838ae90d..a41e0ea766061 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js @@ -58,13 +58,21 @@ export default function usePatternCategories() { // Update the category counts to reflect user registered patterns. userPatterns.forEach( ( pattern ) => { - pattern.categories?.forEach( ( category ) => { + pattern.wp_pattern_category?.forEach( ( catId ) => { + const category = userPatternCategories.find( + ( cat ) => cat.id === catId + )?.name; if ( categoryMap[ category ] ) { categoryMap[ category ].count += 1; } } ); // If the pattern has no categories, add it to uncategorized. - if ( ! pattern.categories?.length ) { + if ( + ! pattern.wp_pattern_category?.length || + ! pattern.wp_pattern_category.some( ( catId ) => + userPatternCategories.find( ( cat ) => cat.id === catId ) + ) + ) { categoryMap.uncategorized.count += 1; } } ); diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 8050999e8a71b..c750c202e76fe 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -33,6 +33,7 @@ import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { exportPatternAsJSONAction } from './export-pattern-action'; import { CreateTemplatePartModalContents } from '../create-template-part-modal'; +import { getItemTitle } from '../../dataviews/actions/utils'; // Patterns. const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } = @@ -72,13 +73,6 @@ function isTemplateRemovable( template ) { ); } -function getItemTitle( item ) { - if ( typeof item.title === 'string' ) { - return decodeEntities( item.title ); - } - return decodeEntities( item.title?.rendered || '' ); -} - const trashPostAction = { id: 'move-to-trash', label: __( 'Move to Trash' ), @@ -805,10 +799,8 @@ export const duplicatePatternAction = { modalHeader: _x( 'Duplicate pattern', 'action label' ), RenderModal: ( { items, closeModal } ) => { const [ item ] = items; - const isThemePattern = item.type === PATTERN_TYPES.theme; const duplicatedProps = useDuplicatePatternProps( { - pattern: - isThemePattern || ! item.patternPost ? item : item.patternPost, + pattern: item, onSuccess: () => closeModal(), } ); return ( diff --git a/packages/editor/src/components/post-actions/export-pattern-action.js b/packages/editor/src/components/post-actions/export-pattern-action.js index deeac4314f653..959cfe1a4abc7 100644 --- a/packages/editor/src/components/post-actions/export-pattern-action.js +++ b/packages/editor/src/components/post-actions/export-pattern-action.js @@ -15,6 +15,7 @@ import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { getItemTitle } from '../../dataviews/actions/utils'; // Patterns. const { PATTERN_TYPES } = unlock( patternsPrivateApis ); @@ -23,9 +24,9 @@ function getJsonFromItem( item ) { return JSON.stringify( { __file: item.type, - title: item.title || item.name, - content: item.patternPost.content.raw, - syncStatus: item.patternPost.wp_pattern_sync_status, + title: getItemTitle( item ), + content: item.content.raw, + syncStatus: item.wp_pattern_sync_status, }, null, 2 @@ -45,14 +46,16 @@ export const exportPatternAsJSONAction = { callback: async ( items ) => { if ( items.length === 1 ) { return downloadBlob( - `${ kebabCase( items[ 0 ].title || items[ 0 ].name ) }.json`, + `${ kebabCase( + getItemTitle( items[ 0 ] ) || items[ 0 ].slug + ) }.json`, getJsonFromItem( items[ 0 ] ), 'application/json' ); } const nameCount = {}; const filesToZip = items.map( ( item ) => { - const name = kebabCase( item.title || item.name ); + const name = kebabCase( getItemTitle( item ) || item.slug ); nameCount[ name ] = ( nameCount[ name ] || 0 ) + 1; return { name: `${ diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts index efbb7c590e651..56c8c9f54c850 100644 --- a/packages/editor/src/dataviews/actions/utils.ts +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -24,7 +24,13 @@ export function getItemTitle( item: Post ) { if ( typeof item.title === 'string' ) { return decodeEntities( item.title ); } - return decodeEntities( item.title?.rendered || '' ); + if ( 'rendered' in item.title ) { + return decodeEntities( item.title.rendered ); + } + if ( 'raw' in item.title ) { + return decodeEntities( item.title.raw ); + } + return ''; } /** diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 29f4358456324..3d8906284a597 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -9,7 +9,7 @@ type PostStatus = export interface BasePost { status?: PostStatus; - title: string | { rendered: string }; + title: string | { rendered: string } | { raw: string }; type: string; id: string | number; } From b8344b66ae422d1e56dbf56c704faf5aeaeddf58 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 5 Jul 2024 14:43:57 +0200 Subject: [PATCH 30/42] BaseControl: forward ref on VisualLabel (#63169) * BaseControl: forward ref on VisualLabel * Remove unnecessary @ts-expect-error * CHANGELOG --- packages/components/CHANGELOG.md | 1 + .../components/src/base-control/index.tsx | 22 ++++++++++++++----- .../src/base-control/stories/index.story.tsx | 1 - 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 21cf827da05de..0c1195083e30d 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements - `Toolbar`: Add support for `vertical` orientation ([#60123](https://github.com/WordPress/gutenberg/pull/60123)). +- `BaseControl`: forward ref on `VisualLabel` ([#63169](https://github.com/WordPress/gutenberg/pull/63169)). ### Bug Fixes diff --git a/packages/components/src/base-control/index.tsx b/packages/components/src/base-control/index.tsx index 2de6ced0a5465..14ecce1bdd729 100644 --- a/packages/components/src/base-control/index.tsx +++ b/packages/components/src/base-control/index.tsx @@ -2,6 +2,12 @@ * External dependencies */ import clsx from 'clsx'; +import type { ForwardedRef } from 'react'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies @@ -116,14 +122,16 @@ const UnconnectedBaseControl = ( * * ); */ -export const VisualLabel = ( { - className, - children, - ...props -}: WordPressComponentProps< BaseControlVisualLabelProps, 'span' > ) => { +const UnforwardedVisualLabel = ( + props: WordPressComponentProps< BaseControlVisualLabelProps, 'span' >, + ref: ForwardedRef< any > +) => { + const { className, children, ...restProps } = props; + return ( { children } @@ -131,6 +139,8 @@ export const VisualLabel = ( { ); }; +export const VisualLabel = forwardRef( UnforwardedVisualLabel ); + export const BaseControl = Object.assign( contextConnectWithoutRef( UnconnectedBaseControl, 'BaseControl' ), { VisualLabel } diff --git a/packages/components/src/base-control/stories/index.story.tsx b/packages/components/src/base-control/stories/index.story.tsx index 3b6e228bd4fc8..62191f906a4ce 100644 --- a/packages/components/src/base-control/stories/index.story.tsx +++ b/packages/components/src/base-control/stories/index.story.tsx @@ -55,7 +55,6 @@ WithHelpText.args = { * otherwise use if the `label` prop was passed. */ export const WithVisualLabel: StoryFn< typeof BaseControl > = ( props ) => { - // @ts-expect-error - Unclear how to fix, see also https://github.com/WordPress/gutenberg/pull/39468#discussion_r827150516 BaseControl.VisualLabel.displayName = 'BaseControl.VisualLabel'; return ( From 31a3c5d35f49b1051ca240d8106b305aa6408ccf Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 5 Jul 2024 14:55:44 +0200 Subject: [PATCH 31/42] DataViews: Register the export pattern action like any third-party action (#63046) Co-authored-by: youknowriad Co-authored-by: ntsekouras --- .../src/components/post-actions/actions.js | 2 -- .../export-pattern-action.native.js | 4 ---- .../actions/export-pattern.tsx} | 23 +++++++------------ .../editor/src/dataviews/actions/index.ts | 2 ++ .../editor/src/dataviews/store/reducer.ts | 16 +++++++++---- packages/editor/src/dataviews/types.ts | 11 ++++++++- 6 files changed, 32 insertions(+), 26 deletions(-) delete mode 100644 packages/editor/src/components/post-actions/export-pattern-action.native.js rename packages/editor/src/{components/post-actions/export-pattern-action.js => dataviews/actions/export-pattern.tsx} (73%) diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index c750c202e76fe..94e38d4ed30f0 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -31,7 +31,6 @@ import { } from '../../store/constants'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; -import { exportPatternAsJSONAction } from './export-pattern-action'; import { CreateTemplatePartModalContents } from '../create-template-part-modal'; import { getItemTitle } from '../../dataviews/actions/utils'; @@ -914,7 +913,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { duplicateTemplatePartAction, isPattern && userCanCreatePostType && duplicatePatternAction, supportsTitle && renamePostActionForPostType, - isPattern && exportPatternAsJSONAction, ! isTemplateOrTemplatePart && restorePostActionForPostType, ! isTemplateOrTemplatePart && ! isPattern && diff --git a/packages/editor/src/components/post-actions/export-pattern-action.native.js b/packages/editor/src/components/post-actions/export-pattern-action.native.js deleted file mode 100644 index b09f5a034a552..0000000000000 --- a/packages/editor/src/components/post-actions/export-pattern-action.native.js +++ /dev/null @@ -1,4 +0,0 @@ -// Client-zip is meant to be used in a browser and is therefore released as an ES6 module only, -// in order for the native build to succeed we are importing a null action and avoiding importing -// the non working in native context client-zip module. -export const exportPatternAsJSONAction = null; diff --git a/packages/editor/src/components/post-actions/export-pattern-action.js b/packages/editor/src/dataviews/actions/export-pattern.tsx similarity index 73% rename from packages/editor/src/components/post-actions/export-pattern-action.js rename to packages/editor/src/dataviews/actions/export-pattern.tsx index 959cfe1a4abc7..917b284ade1a2 100644 --- a/packages/editor/src/components/post-actions/export-pattern-action.js +++ b/packages/editor/src/dataviews/actions/export-pattern.tsx @@ -9,18 +9,15 @@ import { downloadZip } from 'client-zip'; */ import { downloadBlob } from '@wordpress/blob'; import { __ } from '@wordpress/i18n'; -import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; +import type { Action } from '@wordpress/dataviews'; /** * Internal dependencies */ -import { unlock } from '../../lock-unlock'; -import { getItemTitle } from '../../dataviews/actions/utils'; +import type { Pattern } from '../types'; +import { getItemTitle } from './utils'; -// Patterns. -const { PATTERN_TYPES } = unlock( patternsPrivateApis ); - -function getJsonFromItem( item ) { +function getJsonFromItem( item: Pattern ) { return JSON.stringify( { __file: item.type, @@ -33,16 +30,10 @@ function getJsonFromItem( item ) { ); } -export const exportPatternAsJSONAction = { +const exportPattern: Action< Pattern > = { id: 'export-pattern', label: __( 'Export as JSON' ), supportsBulk: true, - isEligible: ( item ) => { - if ( ! item.type ) { - return false; - } - return item.type === PATTERN_TYPES.user; - }, callback: async ( items ) => { if ( items.length === 1 ) { return downloadBlob( @@ -53,7 +44,7 @@ export const exportPatternAsJSONAction = { 'application/json' ); } - const nameCount = {}; + const nameCount: Record< string, number > = {}; const filesToZip = items.map( ( item ) => { const name = kebabCase( getItemTitle( item ) || item.slug ); nameCount[ name ] = ( nameCount[ name ] || 0 ) + 1; @@ -75,3 +66,5 @@ export const exportPatternAsJSONAction = { ); }, }; + +export default exportPattern; diff --git a/packages/editor/src/dataviews/actions/index.ts b/packages/editor/src/dataviews/actions/index.ts index 7442c529f8571..730edf323578c 100644 --- a/packages/editor/src/dataviews/actions/index.ts +++ b/packages/editor/src/dataviews/actions/index.ts @@ -7,6 +7,7 @@ import { type StoreDescriptor, dispatch } from '@wordpress/data'; * Internal dependencies */ import deletePost from './delete-post'; +import exportPattern from './export-pattern'; import resetPost from './reset-post'; // @ts-ignore @@ -18,6 +19,7 @@ export default function registerDefaultActions() { dispatch( editorStore as StoreDescriptor ) ); + registerEntityAction( 'postType', 'wp_block', exportPattern ); registerEntityAction( 'postType', '*', resetPost ); registerEntityAction( 'postType', '*', deletePost ); } diff --git a/packages/editor/src/dataviews/store/reducer.ts b/packages/editor/src/dataviews/store/reducer.ts index e00af778ddf01..0e66fc0fcac72 100644 --- a/packages/editor/src/dataviews/store/reducer.ts +++ b/packages/editor/src/dataviews/store/reducer.ts @@ -19,8 +19,13 @@ function actions( state: ActionState = {}, action: ReduxAction ) { return { ...state, [ action.kind ]: { + ...state[ action.kind ], [ action.name ]: [ - ...( state[ action.kind ]?.[ action.name ] ?? [] ), + ...( + state[ action.kind ]?.[ action.name ] ?? [] + ).filter( + ( _action ) => _action.id !== action.config.id + ), action.config, ], }, @@ -28,9 +33,12 @@ function actions( state: ActionState = {}, action: ReduxAction ) { case 'UNREGISTER_ENTITY_ACTION': { return { ...state, - [ action.kind ]: ( - state[ action.kind ]?.[ action.name ] ?? [] - ).filter( ( _action ) => _action.id !== action.actionId ), + [ action.kind ]: { + ...state[ action.kind ], + [ action.name ]: ( + state[ action.kind ]?.[ action.name ] ?? [] + ).filter( ( _action ) => _action.id !== action.actionId ), + }, }; } } diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 3d8906284a597..02ce996402578 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -20,7 +20,16 @@ export interface TemplateOrTemplatePart extends BasePost { id: string; } -export type Post = TemplateOrTemplatePart | BasePost; +export interface Pattern extends BasePost { + slug: string; + title: { raw: string }; + content: { + raw: string; + }; + wp_pattern_sync_status: string; +} + +export type Post = TemplateOrTemplatePart | Pattern | BasePost; // Will be unnecessary after typescript 5.0 upgrade. export type CoreDataError = { message?: string; code?: string }; From 6034e900bcb52ca6de20e298d2180977eeedcad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:06:57 +0200 Subject: [PATCH 32/42] DataViews's filterSortAndPaginate utility: support sorting by number (#63187) Co-authored-by: oandregal Co-authored-by: ntsekouras --- .../dataviews/src/filter-and-sort-data-view.ts | 12 +++++++++++- packages/dataviews/src/stories/fixtures.js | 16 ++++++++++++++++ .../src/test/filter-and-sort-data-view.js | 17 ++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/filter-and-sort-data-view.ts b/packages/dataviews/src/filter-and-sort-data-view.ts index a62cdcccf6b86..83b005e0fb354 100644 --- a/packages/dataviews/src/filter-and-sort-data-view.ts +++ b/packages/dataviews/src/filter-and-sort-data-view.ts @@ -61,7 +61,7 @@ export function filterSortAndPaginate< Item >( } ); } - if ( view.filters.length > 0 ) { + if ( view.filters?.length > 0 ) { view.filters.forEach( ( filter ) => { const field = _fields.find( ( _field ) => _field.id === filter.field @@ -142,6 +142,16 @@ export function filterSortAndPaginate< Item >( filteredData.sort( ( a, b ) => { const valueA = fieldToSort.getValue( { item: a } ) ?? ''; const valueB = fieldToSort.getValue( { item: b } ) ?? ''; + + if ( + typeof valueA === 'number' && + typeof valueB === 'number' + ) { + return view.sort?.direction === 'asc' + ? valueA - valueB + : valueB - valueA; + } + return view.sort?.direction === 'asc' ? valueA.localeCompare( valueB ) : valueB.localeCompare( valueA ); diff --git a/packages/dataviews/src/stories/fixtures.js b/packages/dataviews/src/stories/fixtures.js index 4117f7a3587dc..133f8d3fea573 100644 --- a/packages/dataviews/src/stories/fixtures.js +++ b/packages/dataviews/src/stories/fixtures.js @@ -22,6 +22,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Not a planet', categories: [ 'Space', 'NASA' ], + satellites: 0, }, { id: 2, @@ -30,6 +31,7 @@ export const data = [ image: 'https://live.staticflickr.com/5678/21911065441_92e2d44708_b.jpg', type: 'Not a planet', categories: [ 'Space' ], + satellites: 0, }, { id: 3, @@ -38,6 +40,7 @@ export const data = [ image: 'https://live.staticflickr.com/742/21712365770_8f70a2c91e_b.jpg', type: 'Not a planet', categories: [ 'NASA' ], + satellites: 0, }, { id: 4, @@ -46,6 +49,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Ice giant', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 14, }, { id: 5, @@ -54,6 +58,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 0, }, { id: 6, @@ -62,6 +67,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 0, }, { id: 7, @@ -70,6 +76,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 1, }, { id: 8, @@ -78,6 +85,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 2, }, { id: 9, @@ -86,6 +94,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Gas giant', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 95, }, { id: 10, @@ -94,6 +103,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Gas giant', categories: [ 'Space', 'Planet', 'Solar system' ], + satellites: 146, }, { id: 11, @@ -102,6 +112,7 @@ export const data = [ image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg', type: 'Ice giant', categories: [ 'Space', 'Ice giant', 'Solar system' ], + satellites: 28, }, ]; @@ -178,6 +189,11 @@ export const fields = [ { value: 'Gas giant', label: 'Gas giant' }, ], }, + { + header: 'Satellites', + id: 'satellites', + enableSorting: true, + }, { header: 'Description', id: 'description', diff --git a/packages/dataviews/src/test/filter-and-sort-data-view.js b/packages/dataviews/src/test/filter-and-sort-data-view.js index 763502762441d..fd15f4ebcc8e7 100644 --- a/packages/dataviews/src/test/filter-and-sort-data-view.js +++ b/packages/dataviews/src/test/filter-and-sort-data-view.js @@ -233,7 +233,7 @@ describe( 'filters', () => { } ); describe( 'sorting', () => { - it( 'should sort', () => { + it( 'should sort by string', () => { const { data: result } = filterSortAndPaginate( data, { @@ -252,6 +252,21 @@ describe( 'sorting', () => { expect( result[ 0 ].title ).toBe( 'Uranus' ); expect( result[ 1 ].title ).toBe( 'Neptune' ); } ); + + it( 'should sort by number', () => { + const { data: result } = filterSortAndPaginate( + data, + { + sort: { field: 'satellites', direction: 'desc' }, + }, + fields + ); + + expect( result ).toHaveLength( 11 ); + expect( result[ 0 ].title ).toBe( 'Saturn' ); + expect( result[ 1 ].title ).toBe( 'Jupiter' ); + expect( result[ 2 ].title ).toBe( 'Uranus' ); + } ); } ); describe( 'pagination', () => { From 8ba6ff344bd7c42bd225e78f778d36dccb480781 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 5 Jul 2024 16:38:53 +0200 Subject: [PATCH 33/42] DateFormatPicker: use CustomSelectControl V2 legacy adapter (#63171) * DateFormatPicker: use CustomSelectControl V2 legacy adapter * Remove unnecessary styles --- .../src/components/date-format-picker/index.js | 11 ++++++++++- .../src/components/date-format-picker/style.scss | 9 --------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/date-format-picker/index.js b/packages/block-editor/src/components/date-format-picker/index.js index 19ec0bf8c24b1..72e6d4831efa9 100644 --- a/packages/block-editor/src/components/date-format-picker/index.js +++ b/packages/block-editor/src/components/date-format-picker/index.js @@ -8,11 +8,20 @@ import { TextControl, ExternalLink, VisuallyHidden, - CustomSelectControl, ToggleControl, __experimentalVStack as VStack, + privateApis as componentsPrivateApis, } from '@wordpress/components'; +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { CustomSelectControlV2Legacy: CustomSelectControl } = unlock( + componentsPrivateApis +); + // So that we illustrate the different formats in the dropdown properly, show a date that is // somwhat recent, has a day greater than 12, and a month with more than three letters. const exampleDate = new Date(); diff --git a/packages/block-editor/src/components/date-format-picker/style.scss b/packages/block-editor/src/components/date-format-picker/style.scss index d1ad52408d350..748e43bb8db94 100644 --- a/packages/block-editor/src/components/date-format-picker/style.scss +++ b/packages/block-editor/src/components/date-format-picker/style.scss @@ -4,13 +4,4 @@ .block-editor-date-format-picker__custom-format-select-control__custom-option { border-top: 1px solid $gray-300; - - &.has-hint { - grid-template-columns: auto 30px; - } - - .components-custom-select-control__item-hint { - grid-row: 2; - text-align: left; - } } From 3a1fb1cf548bba79f408355c3d2be6280a29ff50 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:54:34 +0300 Subject: [PATCH 34/42] DataViews: add performance test for pages (#63170) Co-authored-by: ellatrix Co-authored-by: youknowriad --- .../config/performance-reporter.ts | 3 + test/performance/specs/site-editor.spec.js | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/test/performance/config/performance-reporter.ts b/test/performance/config/performance-reporter.ts index a6d245091c159..999047bdb09b5 100644 --- a/test/performance/config/performance-reporter.ts +++ b/test/performance/config/performance-reporter.ts @@ -34,6 +34,7 @@ export interface WPRawPerformanceResults { inserterSearch: number[]; inserterHover: number[]; loadPatterns: number[]; + loadPages: number[]; listViewOpen: number[]; navigate: number[]; wpBeforeTemplate: number[]; @@ -69,6 +70,7 @@ export interface WPPerformanceResults { inserterSearch?: PerformanceStats; inserterHover?: PerformanceStats; loadPatterns?: PerformanceStats; + loadPages?: PerformanceStats; listViewOpen?: PerformanceStats; navigate?: PerformanceStats; wpBeforeTemplate?: PerformanceStats; @@ -106,6 +108,7 @@ export function curateResults( inserterSearch: stats( results.inserterSearch ), inserterHover: stats( results.inserterHover ), loadPatterns: stats( results.loadPatterns ), + loadPages: stats( results.loadPages ), listViewOpen: stats( results.listViewOpen ), navigate: stats( results.navigate ), wpBeforeTemplate: stats( results.wpBeforeTemplate ), diff --git a/test/performance/specs/site-editor.spec.js b/test/performance/specs/site-editor.spec.js index 9d16f2dff19d4..7b890a4c06775 100644 --- a/test/performance/specs/site-editor.spec.js +++ b/test/performance/specs/site-editor.spec.js @@ -29,6 +29,7 @@ const results = { listViewOpen: [], navigate: [], loadPatterns: [], + loadPages: [], }; test.describe( 'Site Editor Performance', () => { @@ -384,6 +385,68 @@ test.describe( 'Site Editor Performance', () => { } } ); } ); + + test.describe( 'Loading Pages', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyfour' ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyfour' ); + } ); + + const perPage = 20; + + test( 'Run the test', async ( { page, admin, requestUtils } ) => { + await Promise.all( + Array.from( { length: perPage }, async ( el, index ) => { + const { id } = await requestUtils.createPage( { + status: 'publish', + title: `Page (${ index })`, + content: ` + +

Hello

+ + +

Post content

+`, + } ); + + return id; + } ) + ); + + const samples = 10; + for ( let i = 1; i <= samples; i++ ) { + // Start from the trash view, then navigate to all pages, so we + // test item loading rather than site editor load as a whole. + // For some reason `visiSiteEditor` does not work with these + // parameters. + await admin.visitAdminPage( + 'site-editor.php?postType=page&layout=table&activeView=trash' + ); + + const startTime = performance.now(); + + await page.getByRole( 'button', { name: 'All Pages' } ).click(); + + // Wait for all pages to be rendered. + await Promise.all( + Array.from( { length: perPage }, async ( el, index ) => { + return await page + .getByRole( 'link', { + name: `Page (${ index })`, + } ) + .waitFor( { state: 'attached' } ); + } ) + ); + + const endTime = performance.now(); + + results.loadPages.push( endTime - startTime ); + } + } ); + } ); } ); /* eslint-enable playwright/no-conditional-in-test, playwright/expect-expect */ From 99798ec7e814ac12d243f1c297f29a756f1c5118 Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com> Date: Fri, 5 Jul 2024 21:10:22 +0530 Subject: [PATCH 35/42] Rename focal point picker lable for group block (#62438) Co-authored-by: akasunil Co-authored-by: t-hamano Co-authored-by: jasmussen Co-authored-by: jameskoster Co-authored-by: ramonjd Co-authored-by: richtabor Co-authored-by: jennydupuy --- .../src/components/global-styles/background-panel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index 92734b265341a..18ecc26b002d0 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -551,7 +551,7 @@ function BackgroundSizeControls( { Date: Fri, 5 Jul 2024 21:22:40 +0300 Subject: [PATCH 36/42] Perf tests: make pages test compatible with base branch (#63204) Co-authored-by: ellatrix Co-authored-by: youknowriad --- test/performance/specs/site-editor.spec.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/performance/specs/site-editor.spec.js b/test/performance/specs/site-editor.spec.js index 7b890a4c06775..58f2cf5cd4b4e 100644 --- a/test/performance/specs/site-editor.spec.js +++ b/test/performance/specs/site-editor.spec.js @@ -416,6 +416,12 @@ test.describe( 'Site Editor Performance', () => { } ) ); + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Pages' } ).click(); + + // Check if we're dealing with the old URL structure. + const path = new URL( page.url() ).searchParams.get( 'path' ); + const samples = 10; for ( let i = 1; i <= samples; i++ ) { // Start from the trash view, then navigate to all pages, so we @@ -423,7 +429,9 @@ test.describe( 'Site Editor Performance', () => { // For some reason `visiSiteEditor` does not work with these // parameters. await admin.visitAdminPage( - 'site-editor.php?postType=page&layout=table&activeView=trash' + path + ? 'site-editor.php?path=%2Fpage&layout=table&activeView=trash' + : 'site-editor.php?postType=page&layout=table&activeView=trash' ); const startTime = performance.now(); From 16420c4ec6c6868a10d08e46b08612cd726e776b Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:00:58 +0900 Subject: [PATCH 37/42] Raw handling: Remove IE11 fallback code (#63219) Co-authored-by: t-hamano Co-authored-by: ellatrix --- packages/block-editor/src/utils/pasting.js | 17 +++++------------ .../src/api/raw-handling/paste-handler.js | 1 - .../editor/src/components/post-title/index.js | 17 +++++------------ 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/utils/pasting.js b/packages/block-editor/src/utils/pasting.js index c106e78fe8465..2e97849904c68 100644 --- a/packages/block-editor/src/utils/pasting.js +++ b/packages/block-editor/src/utils/pasting.js @@ -52,21 +52,14 @@ export function getPasteEventData( { clipboardData } ) { let plainText = ''; let html = ''; - // IE11 only supports `Text` as an argument for `getData` and will - // otherwise throw an invalid argument error, so we try the standard - // arguments first, then fallback to `Text` if they fail. try { plainText = clipboardData.getData( 'text/plain' ); html = clipboardData.getData( 'text/html' ); - } catch ( error1 ) { - try { - html = clipboardData.getData( 'Text' ); - } catch ( error2 ) { - // Some browsers like UC Browser paste plain text by default and - // don't support clipboardData at all, so allow default - // behaviour. - return; - } + } catch ( error ) { + // Some browsers like UC Browser paste plain text by default and + // don't support clipboardData at all, so allow default + // behaviour. + return; } // Remove Windows-specific metadata appended within copied HTML text. diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index 58bd3f3809a74..002ed7aec0c18 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -111,7 +111,6 @@ export function pasteHandler( { } // Normalize unicode to use composed characters. - // This is unsupported in IE 11 but it's a nice-to-have feature, not mandatory. // Not normalizing the content will only affect older browsers and won't // entirely break the app. // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 08bd07f4b72db..3e333b38eee00 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -85,21 +85,14 @@ function PostTitle( _, forwardedRef ) { let plainText = ''; let html = ''; - // IE11 only supports `Text` as an argument for `getData` and will - // otherwise throw an invalid argument error, so we try the standard - // arguments first, then fallback to `Text` if they fail. try { plainText = clipboardData.getData( 'text/plain' ); html = clipboardData.getData( 'text/html' ); - } catch ( error1 ) { - try { - html = clipboardData.getData( 'Text' ); - } catch ( error2 ) { - // Some browsers like UC Browser paste plain text by default and - // don't support clipboardData at all, so allow default - // behaviour. - return; - } + } catch ( error ) { + // Some browsers like UC Browser paste plain text by default and + // don't support clipboardData at all, so allow default + // behaviour. + return; } // Allows us to ask for this information when we get a report. From 966063e487bbdc82343e7b474624234e958c4570 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 8 Jul 2024 13:26:38 +1000 Subject: [PATCH 38/42] Grid layout: Use numbers instead of strings for layout attributes (#63112) * Grid layout: Use numbers instead of strings for layout attributes Ensure columnCount, rowCount, columnStart, rowStart, columnSpan, and rowSpan are always serialized into block attributes as a number, not a string. Previously we were inconsistent. * I am dumb * Update migration comment * Unset columnCount instead of setting it to null * Unset minimumColumnWidth instead of setting it to an empty string * Show slider at 0 if there is no columnCount set * Handle empty strings and invalid numbers in migration --- .../components/child-layout-control/index.js | 47 +++++++----- .../src/components/grid/grid-item-movers.js | 30 ++------ .../block-editor/src/hooks/layout-child.js | 28 ++++--- packages/block-editor/src/layouts/grid.js | 76 +++++++++++++++---- .../src/api/parser/convert-legacy-block.js | 38 ++++++++++ 5 files changed, 151 insertions(+), 68 deletions(-) diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index 78fc483bb7ff0..698ea2d2d74a4 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -193,8 +193,7 @@ function GridControls( { panelId, } ) { const { columnStart, rowStart, columnSpan, rowSpan } = childLayout; - const { columnCount } = parentLayout ?? {}; - const gridColumnNumber = parseInt( columnCount, 10 ) || 3; + const { columnCount = 3, rowCount } = parentLayout ?? {}; const rootClientId = useSelect( ( select ) => select( blockEditorStore ).getBlockRootClientId( panelId ) ); @@ -202,7 +201,7 @@ function GridControls( { useDispatch( blockEditorStore ); const getNumberOfBlocksBeforeCell = useGetNumberOfBlocksBeforeCell( rootClientId, - gridColumnNumber + columnCount ); const hasStartValue = () => !! columnStart || !! rowStart; const hasSpanValue = () => !! columnSpan || !! rowSpan; @@ -234,14 +233,17 @@ function GridControls( { label={ __( 'Column span' ) } type="number" onChange={ ( value ) => { + // Don't allow unsetting. + const newColumnSpan = + value === '' ? 1 : parseInt( value, 10 ); onChange( { columnStart, rowStart, rowSpan, - columnSpan: value, + columnSpan: newColumnSpan, } ); } } - value={ columnSpan } + value={ columnSpan ?? 1 } min={ 1 } /> { + // Don't allow unsetting. + const newRowSpan = + value === '' ? 1 : parseInt( value, 10 ); onChange( { columnStart, rowStart, columnSpan, - rowSpan: value, + rowSpan: newRowSpan, } ); } } - value={ rowSpan } + value={ rowSpan ?? 1 } min={ 1 } />
@@ -278,8 +283,11 @@ function GridControls( { label={ __( 'Column' ) } type="number" onChange={ ( value ) => { + // Don't allow unsetting. + const newColumnStart = + value === '' ? 1 : parseInt( value, 10 ); onChange( { - columnStart: value, + columnStart: newColumnStart, rowStart, columnSpan, rowSpan, @@ -290,16 +298,16 @@ function GridControls( { rootClientId, rootClientId, getNumberOfBlocksBeforeCell( - value, + newColumnStart, rowStart ) ); } } - value={ columnStart } + value={ columnStart ?? 1 } min={ 1 } max={ - gridColumnNumber - ? gridColumnNumber - ( columnSpan ?? 1 ) + 1 + columnCount + ? columnCount - ( columnSpan ?? 1 ) + 1 : undefined } /> @@ -310,9 +318,12 @@ function GridControls( { label={ __( 'Row' ) } type="number" onChange={ ( value ) => { + // Don't allow unsetting. + const newRowStart = + value === '' ? 1 : parseInt( value, 10 ); onChange( { columnStart, - rowStart: value, + rowStart: newRowStart, columnSpan, rowSpan, } ); @@ -323,17 +334,15 @@ function GridControls( { rootClientId, getNumberOfBlocksBeforeCell( columnStart, - value + newRowStart ) ); } } - value={ rowStart } + value={ rowStart ?? 1 } min={ 1 } max={ - parentLayout?.rowCount - ? parentLayout.rowCount - - ( rowSpan ?? 1 ) + - 1 + rowCount + ? rowCount - ( rowSpan ?? 1 ) + 1 : undefined } /> diff --git a/packages/block-editor/src/components/grid/grid-item-movers.js b/packages/block-editor/src/components/grid/grid-item-movers.js index ed8e35c201f7e..ddfb7dcb2c2dd 100644 --- a/packages/block-editor/src/components/grid/grid-item-movers.js +++ b/packages/block-editor/src/components/grid/grid-item-movers.js @@ -32,13 +32,9 @@ export function GridItemMovers( { const columnCount = parentLayout?.columnCount; const rowCount = parentLayout?.rowCount; - const columnCountNumber = parseInt( columnCount, 10 ); - const rowStartNumber = parseInt( rowStart, 10 ); - const columnStartNumber = parseInt( columnStart, 10 ); - const getNumberOfBlocksBeforeCell = useGetNumberOfBlocksBeforeCell( gridClientId, - columnCountNumber + columnCount ); return ( @@ -56,10 +52,7 @@ export function GridItemMovers( { [ blockClientId ], gridClientId, gridClientId, - getNumberOfBlocksBeforeCell( - columnStartNumber, - rowStartNumber - 1 - ) + getNumberOfBlocksBeforeCell( columnStart, rowStart - 1 ) ); } } /> @@ -76,10 +69,7 @@ export function GridItemMovers( { [ blockClientId ], gridClientId, gridClientId, - getNumberOfBlocksBeforeCell( - columnStartNumber, - rowStartNumber + 1 - ) + getNumberOfBlocksBeforeCell( columnStart, rowStart + 1 ) ); } } /> @@ -89,17 +79,14 @@ export function GridItemMovers( { disabled={ columnStart <= 1 } onClick={ () => { onChange( { - columnStart: columnStartNumber - 1, + columnStart: columnStart - 1, } ); __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ blockClientId ], gridClientId, gridClientId, - getNumberOfBlocksBeforeCell( - columnStartNumber - 1, - rowStartNumber - ) + getNumberOfBlocksBeforeCell( columnStart - 1, rowStart ) ); } } /> @@ -109,17 +96,14 @@ export function GridItemMovers( { disabled={ columnCount && columnEnd >= columnCount } onClick={ () => { onChange( { - columnStart: columnStartNumber + 1, + columnStart: columnStart + 1, } ); __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ blockClientId ], gridClientId, gridClientId, - getNumberOfBlocksBeforeCell( - columnStartNumber + 1, - rowStartNumber - ) + getNumberOfBlocksBeforeCell( columnStart + 1, rowStart ) ); } } /> diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index 06856eaec337d..8d12124aefdbb 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -35,6 +35,23 @@ function useBlockPropsChildLayoutStyles( { style } ) { const id = useInstanceId( useBlockPropsChildLayoutStyles ); const selector = `.wp-container-content-${ id }`; + // Check that the grid layout attributes are of the correct type, so that we don't accidentally + // write code that stores a string attribute instead of a number. + if ( process.env.NODE_ENV === 'development' ) { + if ( columnStart && typeof columnStart !== 'number' ) { + throw new Error( 'columnStart must be a number' ); + } + if ( rowStart && typeof rowStart !== 'number' ) { + throw new Error( 'rowStart must be a number' ); + } + if ( columnSpan && typeof columnSpan !== 'number' ) { + throw new Error( 'columnSpan must be a number' ); + } + if ( rowSpan && typeof rowSpan !== 'number' ) { + throw new Error( 'rowSpan must be a number' ); + } + } + let css = ''; if ( shouldRenderChildLayoutStyles ) { if ( selfStretch === 'fixed' && flexSize ) { @@ -81,16 +98,6 @@ function useBlockPropsChildLayoutStyles( { style } ) { ( columnSpan || columnStart ) && ( minimumColumnWidth || ! columnCount ) ) { - // Check if columnSpan and columnStart are numbers so Math.max doesn't break. - const columnSpanNumber = columnSpan ? parseInt( columnSpan ) : null; - const columnStartNumber = columnStart - ? parseInt( columnStart ) - : null; - const highestNumber = Math.max( - columnSpanNumber, - columnStartNumber - ); - let parentColumnValue = parseFloat( minimumColumnWidth ); /** * 12rem is the default minimumColumnWidth value. @@ -112,6 +119,7 @@ function useBlockPropsChildLayoutStyles( { style } ) { parentColumnUnit = 'rem'; } + const highestNumber = Math.max( columnSpan, columnStart ); const defaultGapValue = parentColumnUnit === 'px' ? 24 : 1.5; const containerQueryValue = highestNumber * parentColumnValue + diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 23b8bb5f3909f..21870fc182093 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -125,6 +125,23 @@ export default { rowCount = null, } = layout; + // Check that the grid layout attributes are of the correct type, so that we don't accidentally + // write code that stores a string attribute instead of a number. + if ( process.env.NODE_ENV === 'development' ) { + if ( + minimumColumnWidth && + typeof minimumColumnWidth !== 'string' + ) { + throw new Error( 'minimumColumnWidth must be a string' ); + } + if ( columnCount && typeof columnCount !== 'number' ) { + throw new Error( 'columnCount must be a number' ); + } + if ( rowCount && typeof rowCount !== 'number' ) { + throw new Error( 'rowCount must be a number' ); + } + } + // If a block's block.json skips serialization for spacing or spacing.blockGap, // don't apply the user-defined value to the styles. const blockGapValue = @@ -241,7 +258,8 @@ function GridLayoutMinimumWidthControl( { layout, onChange } ) { onChange={ ( newValue ) => { onChange( { ...layout, - minimumColumnWidth: newValue, + minimumColumnWidth: + newValue === '' ? undefined : newValue, } ); } } onUnitChange={ handleUnitChange } @@ -274,7 +292,15 @@ function GridLayoutColumnsAndRowsControl( { onChange, allowSizingOnChildren, } ) { - const { columnCount = 3, rowCount, isManualPlacement } = layout; + // If the grid interactivity experiment is enabled, allow unsetting the column count. + const defaultColumnCount = window.__experimentalEnableGridInteractivity + ? undefined + : 3; + const { + columnCount = defaultColumnCount, + rowCount, + isManualPlacement, + } = layout; return ( <> @@ -290,18 +316,31 @@ function GridLayoutColumnsAndRowsControl( { { - /** - * If the input is cleared, avoid switching - * back to "Auto" by setting a value of "1". - */ - const validValue = value !== '' ? value : '1'; - onChange( { - ...layout, - columnCount: - window.__experimentalEnableGridInteractivity - ? parseInt( value, 10 ) || null - : parseInt( validValue, 10 ), - } ); + if ( + window.__experimentalEnableGridInteractivity + ) { + // Allow unsetting the column count when in auto mode. + const defaultNewColumnCount = + isManualPlacement ? 1 : undefined; + const newColumnCount = + value === '' + ? defaultNewColumnCount + : parseInt( value, 10 ); + onChange( { + ...layout, + columnCount: newColumnCount, + } ); + } else { + // Don't allow unsetting the column count. + const newColumnCount = + value === '' + ? 1 + : parseInt( value, 10 ); + onChange( { + ...layout, + columnCount: newColumnCount, + } ); + } } } value={ columnCount } min={ 0 } @@ -320,9 +359,14 @@ function GridLayoutColumnsAndRowsControl( { { + // Don't allow unsetting the row count. + const newRowCount = + value === '' + ? 1 + : parseInt( value, 10 ); onChange( { ...layout, - rowCount: parseInt( value, 10 ), + rowCount: newRowCount, } ); } } value={ rowCount } @@ -331,7 +375,7 @@ function GridLayoutColumnsAndRowsControl( { /> ) : ( onChange( { ...layout, diff --git a/packages/blocks/src/api/parser/convert-legacy-block.js b/packages/blocks/src/api/parser/convert-legacy-block.js index 055679302efd6..1ddec1d8b3448 100644 --- a/packages/blocks/src/api/parser/convert-legacy-block.js +++ b/packages/blocks/src/api/parser/convert-legacy-block.js @@ -77,6 +77,44 @@ export function convertLegacyBlockNameAndAttributes( name, attributes ) { newAttributes.legacy = true; } + // Column count was stored as a string from WP 6.3-6.6. Convert it to a number. + if ( + attributes.layout?.type === 'grid' && + typeof attributes.layout?.columnCount === 'string' + ) { + newAttributes.layout = { + ...newAttributes.layout, + columnCount: parseInt( attributes.layout.columnCount, 10 ), + }; + } + + // Column span and row span were stored as strings in WP 6.6. Convert them to numbers. + if ( typeof attributes.style?.layout?.columnSpan === 'string' ) { + const columnSpanNumber = parseInt( + attributes.style.layout.columnSpan, + 10 + ); + newAttributes.style = { + ...newAttributes.style, + layout: { + ...newAttributes.style.layout, + columnSpan: isNaN( columnSpanNumber ) + ? undefined + : columnSpanNumber, + }, + }; + } + if ( typeof attributes.style?.layout?.rowSpan === 'string' ) { + const rowSpanNumber = parseInt( attributes.style.layout.rowSpan, 10 ); + newAttributes.style = { + ...newAttributes.style, + layout: { + ...newAttributes.style.layout, + rowSpan: isNaN( rowSpanNumber ) ? undefined : rowSpanNumber, + }, + }; + } + // The following code is only relevant for the Gutenberg plugin. // It's a stand-alone if statement for dead-code elimination. if ( globalThis.IS_GUTENBERG_PLUGIN ) { From 6d3282e0ba3fb56bb4e55cb797eb5f0d5dae8795 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 8 Jul 2024 06:21:18 +0200 Subject: [PATCH 39/42] Fix unlabeled Remove shadow buttons. (#63197) Replace tooltip and ariaLabel with label to ensure that the "Remove shadow" button is labeled. Co-authored-by: afercia Co-authored-by: t-hamano Co-authored-by: carolinan Co-authored-by: mirka <0mirka00@git.wordpress.org> --- .../src/components/global-styles/shadows-edit-panel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js index 7a513cc250d66..ece57f92237fb 100644 --- a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js @@ -371,8 +371,7 @@ function ShadowItem( { shadow, onChange, canRemove, onRemove } ) { 'edit-site-global-styles__shadow-editor__remove-button', { 'is-open': isOpen } ), - ariaLabel: __( 'Remove shadow' ), - tooltip: __( 'Remove shadow' ), + label: __( 'Remove shadow' ), }; return ( From 0aa455b322fd3a4829ba8eac0faea803776b4bc1 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:26:54 +1000 Subject: [PATCH 40/42] Section Styles: Resolve ref values in variations data (#63172) Co-authored-by: aaronrobertshaw Co-authored-by: andrewserong Co-authored-by: ramonjd Co-authored-by: daviedR --- backport-changelog/6.6/6989.md | 3 + lib/block-supports/block-style-variations.php | 40 ++++++++ .../src/hooks/block-style-variation.js | 83 ++++++++++++++++- .../get-variation-styles-with-ref-values.js | 91 +++++++++++++++++++ .../block-style-variations-test.php | 72 +++++++++++++++ 5 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 backport-changelog/6.6/6989.md create mode 100644 packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js diff --git a/backport-changelog/6.6/6989.md b/backport-changelog/6.6/6989.md new file mode 100644 index 0000000000000..3d236938ff74a --- /dev/null +++ b/backport-changelog/6.6/6989.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6989 + +* https://github.com/WordPress/gutenberg/pull/63172 diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index 1c049f4a0fee5..d1a63915f1288 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -38,6 +38,42 @@ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { return $matches[1] ?? null; } +/** + * Recursively resolves any `ref` values within a block style variation's data. + * + * @since 6.6.0 + * + * @param array $variation_data Reference to the variation data being processed. + * @param array $theme_json Theme.json data to retrieve referenced values from. + */ +function gutenberg_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) { + foreach ( $variation_data as $key => &$value ) { + // Only need to potentially process arrays. + if ( is_array( $value ) ) { + // If ref value is set, attempt to find its matching value and update it. + if ( array_key_exists( 'ref', $value ) ) { + // Clean up any invalid ref value. + if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) { + unset( $variation_data[ $key ] ); + } + + $value_path = explode( '.', $value['ref'] ?? '' ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + + // Only update the current value if the referenced path matched a value. + if ( null === $ref_value ) { + unset( $variation_data[ $key ] ); + } else { + $value = $ref_value; + } + } else { + // Recursively look for ref instances. + gutenberg_resolve_block_style_variation_ref_values( $value, $theme_json ); + } + } + } +} + /** * Render the block style variation's styles. * @@ -79,6 +115,10 @@ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) return $parsed_block; } + // Recursively resolve any ref values with the appropriate value within the + // theme_json data. + gutenberg_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + $variation_instance = gutenberg_create_block_style_variation_instance_name( $parsed_block, $variation ); $class_name = "is-style-$variation_instance"; $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index 21259966d8a63..f302cf2aa3a2a 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -14,6 +14,7 @@ import { getBlockSelectors, } from '../components/global-styles'; import { useStyleOverride } from './utils'; +import { getValueFromObjectPath } from '../utils/object'; import { store as blockEditorStore } from '../store'; import { globalStylesDataKey } from '../store/private-keys'; import { unlock } from '../lock-unlock'; @@ -180,6 +181,77 @@ export function __unstableBlockStyleVariationOverridesWithConfig( { config } ) { ); } +/** + * Retrieves any variation styles data and resolves any referenced values. + * + * @param {Object} globalStyles A complete global styles object, containing settings and styles. + * @param {string} name The name of the desired block type. + * @param {variation} variation The of the block style variation to retrieve data for. + * + * @return {Object|undefined} The global styles data for the specified variation. + */ +export function getVariationStylesWithRefValues( + globalStyles, + name, + variation +) { + if ( ! globalStyles?.styles?.blocks?.[ name ]?.variations?.[ variation ] ) { + return; + } + + // Helper to recursively look for `ref` values to resolve. + const replaceRefs = ( variationStyles ) => { + Object.keys( variationStyles ).forEach( ( key ) => { + const value = variationStyles[ key ]; + + // Only process objects. + if ( typeof value === 'object' && value !== null ) { + // Process `ref` value if present. + if ( value.ref !== undefined ) { + if ( + typeof value.ref !== 'string' || + value.ref.trim() === '' + ) { + // Remove invalid ref. + delete variationStyles[ key ]; + } else { + // Resolve `ref` value. + const refValue = getValueFromObjectPath( + globalStyles, + value.ref + ); + + if ( refValue ) { + variationStyles[ key ] = refValue; + } else { + delete variationStyles[ key ]; + } + } + } else { + // Recursively resolve `ref` values in nested objects. + replaceRefs( value ); + + // After recursion, if value is empty due to explicitly + // `undefined` ref value, remove it. + if ( Object.keys( value ).length === 0 ) { + delete variationStyles[ key ]; + } + } + } + } ); + }; + + // Deep clone variation node to avoid mutating it within global styles and losing refs. + const styles = JSON.parse( + JSON.stringify( + globalStyles.styles.blocks[ name ].variations[ variation ] + ) + ); + replaceRefs( styles ); + + return styles; +} + function useBlockStyleVariation( name, variation, clientId ) { // Prefer global styles data in GlobalStylesContext, which are available // if in the site editor. Otherwise fall back to whatever is in the @@ -194,9 +266,14 @@ function useBlockStyleVariation( name, variation, clientId ) { }, [] ); return useMemo( () => { - const styles = mergedConfig?.styles ?? globalStyles; - const variationStyles = - styles?.blocks?.[ name ]?.variations?.[ variation ]; + const variationStyles = getVariationStylesWithRefValues( + { + settings: mergedConfig?.settings ?? globalSettings, + styles: mergedConfig?.styles ?? globalStyles, + }, + name, + variation + ); return { settings: mergedConfig?.settings ?? globalSettings, diff --git a/packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js b/packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js new file mode 100644 index 0000000000000..e42fcef06122b --- /dev/null +++ b/packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js @@ -0,0 +1,91 @@ +/** + * Internal dependencies + */ +import { getVariationStylesWithRefValues } from '../block-style-variation'; + +describe( 'getVariationStylesWithRefValues', () => { + it( 'should resolve ref values correctly', () => { + const globalStyles = { + styles: { + color: { background: 'red' }, + blocks: { + 'core/heading': { + color: { text: 'blue' }, + }, + 'core/group': { + variations: { + custom: { + color: { + text: { ref: 'styles.does-not-exist' }, + background: { + ref: 'styles.color.background', + }, + }, + blocks: { + 'core/heading': { + color: { + text: { + ref: 'styles.blocks.core/heading.color.text', + }, + background: { ref: '' }, + }, + }, + }, + elements: { + link: { + color: { + text: { + ref: 'styles.elements.link.color.text', + }, + background: { ref: undefined }, + }, + ':hover': { + color: { + text: { + ref: 'styles.elements.link.:hover.color.text', + }, + }, + }, + }, + }, + }, + }, + }, + }, + elements: { + link: { + color: { text: 'green' }, + ':hover': { + color: { text: 'yellow' }, + }, + }, + }, + }, + }; + + expect( + getVariationStylesWithRefValues( + globalStyles, + 'core/group', + 'custom' + ) + ).toEqual( { + color: { background: 'red' }, + blocks: { + 'core/heading': { + color: { text: 'blue' }, + }, + }, + elements: { + link: { + color: { + text: 'green', + }, + ':hover': { + color: { text: 'yellow' }, + }, + }, + }, + } ); + } ); +} ); diff --git a/phpunit/block-supports/block-style-variations-test.php b/phpunit/block-supports/block-style-variations-test.php index 0236beff468ee..350efb3473076 100644 --- a/phpunit/block-supports/block-style-variations-test.php +++ b/phpunit/block-supports/block-style-variations-test.php @@ -189,4 +189,76 @@ public function test_add_registered_block_styles_to_theme_data() { $this->assertSameSetsWithIndex( $expected, $group_styles, 'Variation data does not match' ); } + + /** + * Tests that block style variations resolve any `ref` values when generating styles. + */ + public function test_block_style_variation_ref_values() { + switch_theme( 'block-theme' ); + + $variation_data = array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.does-not-exist', + ), + 'background' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-a.color.text', + ), + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-a.color.background', + ), + 'background' => array( + 'ref' => '', + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-b.color.text', + ), + 'background' => array( + 'ref' => null, + ), + ), + ':hover' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-b.color.background', + ), + ), + ), + ), + ), + ); + + $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_raw_data(); + + gutenberg_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + + $expected = array( + 'color' => array( 'background' => 'plum' ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( 'text' => 'indigo' ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( 'text' => 'lightblue' ), + ':hover' => array( + 'color' => array( 'text' => 'midnightblue' ), + ), + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $variation_data, 'Variation data with resolved ref values does not match' ); + } } From fd62d1a6bc1c4c695bc6f9a849fbca97b2322c32 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Mon, 8 Jul 2024 16:17:56 +1000 Subject: [PATCH 41/42] Only hide drop indicator when grid has `isManualPlacement` set. (#63226) Co-authored-by: tellthemachines Co-authored-by: noisysocks --- packages/block-editor/src/components/inner-blocks/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index d46f28d1e19f2..db947a01a91dc 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -269,7 +269,8 @@ export function useInnerBlocksProps( props = {}, options = {} ) { props.ref, __unstableDisableDropZone || isDropZoneDisabled || - ( layout?.columnCount && window.__experimentalEnableGridInteractivity ) + ( layout?.isManualPlacement && + window.__experimentalEnableGridInteractivity ) ? null : blockDropZoneRef, ] ); From abf041fc66e5da99677cc4e467d5622381328bbc Mon Sep 17 00:00:00 2001 From: Rich Tabor Date: Mon, 8 Jul 2024 02:20:50 -0400 Subject: [PATCH 42/42] Include alignwide in nested has-outer-padding logic (#63207) Co-authored-by: richtabor Co-authored-by: tellthemachines Co-authored-by: andrewserong Co-authored-by: annezazu Co-authored-by: carolinan Co-authored-by: YanCol --- backport-changelog/6.6/6987.md | 3 +++ lib/class-wp-theme-json-gutenberg.php | 6 +++--- .../components/global-styles/use-global-styles-output.js | 4 ++-- phpunit/class-wp-theme-json-test.php | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 backport-changelog/6.6/6987.md diff --git a/backport-changelog/6.6/6987.md b/backport-changelog/6.6/6987.md new file mode 100644 index 0000000000000..c3bf36f8f9933 --- /dev/null +++ b/backport-changelog/6.6/6987.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6987 + +* https://github.com/WordPress/gutenberg/pull/63207 \ No newline at end of file diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index bd74852c52074..15dbb14896ec1 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3000,10 +3000,10 @@ public function get_root_layout_rules( $selector, $block_metadata ) { $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; // Alignfull children of the container with left and right padding have negative margins so they can still be full width. $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; - // Nested children of the container with left and right padding that are not wide or full aligned do not get padding, unless they are direct children of an alignfull flow container. - $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; }'; + // Nested children of the container with left and right padding that are not full aligned do not get padding, unless they are direct children of an alignfull flow container. + $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }'; // Alignfull direct children of the containers that are targeted by the rule above do not need negative margins. - $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; }'; + $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }'; } $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index d9fc247db1794..6756398708161 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -939,8 +939,8 @@ export const toStyles = ( ruleset += `padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) } .has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } .has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); } - .has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; } - .has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; + .has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; } + .has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; `; } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 2d6d4dbb668c9..81abefc4ad15b 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -3579,7 +3579,7 @@ public function test_get_styles_for_block_with_padding_aware_alignments() { 'selector' => 'body', ); - $expected = ':where(body) { margin: 0; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; }.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(body){--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; + $expected = ':where(body) { margin: 0; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(body){--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON_Gutenberg::ROOT_BLOCK_SELECTOR, $metadata ); $style_rules = $theme_json->get_styles_for_block( $metadata ); $this->assertSame( $expected, $root_rules . $style_rules );