From b17cc77cd0093f58d3e6d9e54a025e2447cf0bfb Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Thu, 25 Apr 2024 12:20:58 +0300 Subject: [PATCH 01/97] Editor: Update post excerpt panel with new designs (#60894) Co-authored-by: ntsekouras Co-authored-by: jameskoster Co-authored-by: youknowriad Co-authored-by: paulwilde --- .../components/sidebar/post-status/index.js | 38 ++-- .../sidebar/settings-sidebar/index.js | 2 - .../src/components/sidebar-edit-mode/index.js | 2 - .../page-panels/page-summary.js | 5 + packages/editor/README.md | 8 +- .../src/components/post-card-panel/index.js | 93 ++++----- .../src/components/post-excerpt/check.js | 18 -- .../src/components/post-excerpt/index.js | 81 ++++++-- .../src/components/post-excerpt/panel.js | 181 +++++++++++++++++- .../src/components/post-excerpt/style.scss | 24 +++ packages/editor/src/private-apis.js | 2 + .../specs/editor/plugins/meta-boxes.spec.js | 17 +- .../various/new-post-default-content.spec.js | 4 +- test/e2e/specs/editor/various/sidebar.spec.js | 12 +- 14 files changed, 373 insertions(+), 114 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index c5a2350ed04117..dab8ec2688b3a2 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -28,7 +28,8 @@ import PostSlug from '../post-slug'; import PostFormat from '../post-format'; import { unlock } from '../../../lock-unlock'; -const { PostStatus: PostStatusPanel } = unlock( editorPrivateApis ); +const { PostStatus: PostStatusPanel, PrivatePostExcerptPanel } = + unlock( editorPrivateApis ); /** * Module Constants @@ -36,16 +37,30 @@ const { PostStatus: PostStatusPanel } = unlock( editorPrivateApis ); const PANEL_NAME = 'post-status'; export default function PostStatus() { - const { isOpened, isRemoved } = useSelect( ( select ) => { - // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do - // not use isEditorPanelEnabled since this panel should not be disabled through the UI. - const { isEditorPanelRemoved, isEditorPanelOpened } = - select( editorStore ); - return { - isRemoved: isEditorPanelRemoved( PANEL_NAME ), - isOpened: isEditorPanelOpened( PANEL_NAME ), - }; - }, [] ); + const { isOpened, isRemoved, showPostExcerptPanel } = useSelect( + ( select ) => { + // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do + // not use isEditorPanelEnabled since this panel should not be disabled through the UI. + const { + isEditorPanelRemoved, + isEditorPanelOpened, + getCurrentPostType, + } = select( editorStore ); + const postType = getCurrentPostType(); + return { + isRemoved: isEditorPanelRemoved( PANEL_NAME ), + isOpened: isEditorPanelOpened( PANEL_NAME ), + // Post excerpt panel is rendered in different place depending on the post type. + // So we cannot make this check inside the PostExcerpt component based on the current edited entity. + showPostExcerptPanel: ! [ + 'wp_template', + 'wp_template_part', + 'wp_block', + ].includes( postType ), + }; + }, + [] + ); const { toggleEditorPanelOpened } = useDispatch( editorStore ); if ( isRemoved ) { @@ -64,6 +79,7 @@ export default function PostStatus() { <> + { showPostExcerptPanel && } diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index 7c0e6fea68aaaa..fd5b136ba461d6 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -22,7 +22,6 @@ import { PluginDocumentSettingPanel, PluginSidebar, PostDiscussionPanel, - PostExcerptPanel, PostLastRevisionPanel, PostTaxonomiesPanel, privateApis as editorPrivateApis, @@ -172,7 +171,6 @@ const SidebarContent = ( { tabName, keyboardShortcut, isEditingTemplate } ) => { - diff --git a/packages/edit-site/src/components/sidebar-edit-mode/index.js b/packages/edit-site/src/components/sidebar-edit-mode/index.js index 7ac2098a00c1be..8541f952abbf41 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/index.js @@ -14,7 +14,6 @@ import { store as coreStore } from '@wordpress/core-data'; import { PageAttributesPanel, PostDiscussionPanel, - PostExcerptPanel, PostLastRevisionPanel, PostTaxonomiesPanel, privateApis as editorPrivateApis, @@ -100,7 +99,6 @@ const FillContents = ( { tabName, isEditingPage, supportsGlobalStyles } ) => { { isEditingPage ? : } - diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js index 5ca010e2faccba..5b8710ac3abe42 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js @@ -9,12 +9,16 @@ import { PostSchedulePanel, PostTemplatePanel, PostFeaturedImagePanel, + privateApis as editorPrivateApis, } from '@wordpress/editor'; /** * Internal dependencies */ import PageStatus from './page-status'; +import { unlock } from '../../../lock-unlock'; + +const { PrivatePostExcerptPanel } = unlock( editorPrivateApis ); export default function PageSummary( { status, @@ -29,6 +33,7 @@ export default function PageSummary( { { ( fills ) => ( <> + { - const { - getEditedPostAttribute, - getCurrentPostType, - getCurrentPostId, - __experimentalGetTemplateInfo, - } = select( editorStore ); - const { getEditedEntityRecord, getEntityRecord } = - select( coreStore ); - const siteSettings = getEntityRecord( 'root', 'site' ); - const _type = getCurrentPostType(); - const _id = getCurrentPostId(); - const _record = getEditedEntityRecord( 'postType', _type, _id ); - const _templateInfo = __experimentalGetTemplateInfo( _record ); - return { - title: - _templateInfo?.title || getEditedPostAttribute( 'title' ), - modified: getEditedPostAttribute( 'modified' ), - id: _id, - postType: _type, - templateInfo: _templateInfo, - icon: unlock( select( editorStore ) ).getPostIcon( _type, { - area: _record?.area, - } ), - isPostsPage: +_id === siteSettings?.page_for_posts, - }; - }, [] ); - const description = templateInfo?.description; + const { + modified, + title, + showPostExcerptPanel, + icon, + postType, + isPostsPage, + } = useSelect( ( select ) => { + const { + getEditedPostAttribute, + getCurrentPostType, + getCurrentPostId, + __experimentalGetTemplateInfo, + } = select( editorStore ); + const { getEditedEntityRecord, getEntityRecord } = select( coreStore ); + const siteSettings = getEntityRecord( 'root', 'site' ); + const _type = getCurrentPostType(); + const _id = getCurrentPostId(); + const _record = getEditedEntityRecord( 'postType', _type, _id ); + const _templateInfo = + [ TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE ].includes( _type ) && + __experimentalGetTemplateInfo( _record ); + return { + title: _templateInfo?.title || getEditedPostAttribute( 'title' ), + modified: getEditedPostAttribute( 'modified' ), + id: _id, + postType: _type, + icon: unlock( select( editorStore ) ).getPostIcon( _type, { + area: _record?.area, + } ), + isPostsPage: +_id === siteSettings?.page_for_posts, + // Post excerpt panel is rendered in different place depending on the post type. + // So we cannot make this check inside the PostExcerpt component based on the current edited entity. + showPostExcerptPanel: [ + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, + PATTERN_POST_TYPE, + ].includes( _type ), + }; + }, [] ); const lastEditedText = modified && sprintf( @@ -98,20 +111,14 @@ export default function PostCardPanel( { className, actions } ) { { actions } - { ( description || - lastEditedText || - showPostContentInfo ) && ( - - { description && { description } } - { showPostContentInfo && } - { lastEditedText && ( - { lastEditedText } - ) } - - ) } + + { showPostExcerptPanel && } + { showPostContentInfo && } + { lastEditedText && { lastEditedText } } + { postType === TEMPLATE_POST_TYPE && } diff --git a/packages/editor/src/components/post-excerpt/check.js b/packages/editor/src/components/post-excerpt/check.js index d1d125428f58bd..77436ecfed218a 100644 --- a/packages/editor/src/components/post-excerpt/check.js +++ b/packages/editor/src/components/post-excerpt/check.js @@ -1,13 +1,7 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; - /** * Internal dependencies */ import PostTypeSupportCheck from '../post-type-support-check'; -import { store as editorStore } from '../../store'; /** * Component for checking if the post type supports the excerpt field. @@ -18,18 +12,6 @@ import { store as editorStore } from '../../store'; * @return {Component} The component to be rendered. */ function PostExcerptCheck( { children } ) { - const postType = useSelect( ( select ) => { - const { getEditedPostAttribute } = select( editorStore ); - return getEditedPostAttribute( 'type' ); - }, [] ); - - // This special case is unfortunate, but the REST API of wp_template and wp_template_part - // support the excerpt field throught the "description" field rather than "excerpt" which means - // the default ExcerptPanel won't work for these. - if ( [ 'wp_template', 'wp_template_part' ].includes( postType ) ) { - return null; - } - return ( { children } diff --git a/packages/editor/src/components/post-excerpt/index.js b/packages/editor/src/components/post-excerpt/index.js index 47a1d3bf585850..cdfb27c4631cc8 100644 --- a/packages/editor/src/components/post-excerpt/index.js +++ b/packages/editor/src/components/post-excerpt/index.js @@ -4,37 +4,88 @@ import { __ } from '@wordpress/i18n'; import { ExternalLink, TextareaControl } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -function PostExcerpt() { - const excerpt = useSelect( - ( select ) => select( editorStore ).getEditedPostAttribute( 'excerpt' ), +/** + * Renders an editable textarea for the post excerpt. + * Templates, template parts and patterns use the `excerpt` field as a description semantically. + * Additionally templates and template parts override the `excerpt` field as `description` in + * REST API. So this component handles proper labeling and updating the edited entity. + * + * @param {Object} props - Component props. + * @param {boolean} [props.hideLabelFromVision=false] - Whether to visually hide the textarea's label. + * @param {boolean} [props.updateOnBlur=false] - Whether to update the post on change or use local state and update on blur. + */ +export default function PostExcerpt( { + hideLabelFromVision = false, + updateOnBlur = false, +} ) { + const { excerpt, shouldUseDescriptionLabel, usedAttribute } = useSelect( + ( select ) => { + const { getCurrentPostType, getEditedPostAttribute } = + select( editorStore ); + const postType = getCurrentPostType(); + // This special case is unfortunate, but the REST API of wp_template and wp_template_part + // support the excerpt field throught the "description" field rather than "excerpt". + const _usedAttribute = [ + 'wp_template', + 'wp_template_part', + ].includes( postType ) + ? 'description' + : 'excerpt'; + return { + excerpt: getEditedPostAttribute( _usedAttribute ), + // There are special cases where we want to label the excerpt as a description. + shouldUseDescriptionLabel: [ + 'wp_template', + 'wp_template_part', + 'wp_block', + ].includes( postType ), + usedAttribute: _usedAttribute, + }; + }, [] ); const { editPost } = useDispatch( editorStore ); + const [ localExcerpt, setLocalExcerpt ] = useState( excerpt ); + const updatePost = ( value ) => { + editPost( { [ usedAttribute ]: value } ); + }; + const label = shouldUseDescriptionLabel + ? __( 'Write a description (optional)' ) + : __( 'Write an excerpt (optional)' ); return (
editPost( { excerpt: value } ) } - value={ excerpt } + onChange={ updateOnBlur ? setLocalExcerpt : updatePost } + onBlur={ + updateOnBlur ? () => updatePost( localExcerpt ) : undefined + } + value={ updateOnBlur ? localExcerpt : excerpt } + help={ + ! shouldUseDescriptionLabel ? ( + + { __( 'Learn more about manual excerpts' ) } + + ) : ( + __( 'Write a description' ) + ) + } /> - - { __( 'Learn more about manual excerpts' ) } -
); } - -export default PostExcerpt; diff --git a/packages/editor/src/components/post-excerpt/panel.js b/packages/editor/src/components/post-excerpt/panel.js index 63149a05222385..36646e07b4ce8b 100644 --- a/packages/editor/src/components/post-excerpt/panel.js +++ b/packages/editor/src/components/post-excerpt/panel.js @@ -1,9 +1,23 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { PanelBody } from '@wordpress/components'; +import { + PanelBody, + __experimentalText as Text, + Dropdown, + Button, + __experimentalVStack as VStack, +} from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useMemo, useState } from '@wordpress/element'; +import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -11,6 +25,7 @@ import { useDispatch, useSelect } from '@wordpress/data'; import PostExcerptForm from './index'; import PostExcerptCheck from './check'; import PluginPostExcerpt from './plugin'; +import { TEMPLATE_ORIGINS } from '../../store/constants'; import { store as editorStore } from '../../store'; /** @@ -19,13 +34,17 @@ import { store as editorStore } from '../../store'; const PANEL_NAME = 'post-excerpt'; function ExcerptPanel() { - const { isOpened, isEnabled } = useSelect( ( select ) => { - const { isEditorPanelOpened, isEditorPanelEnabled } = - select( editorStore ); + const { isOpened, isEnabled, postType } = useSelect( ( select ) => { + const { + isEditorPanelOpened, + isEditorPanelEnabled, + getCurrentPostType, + } = select( editorStore ); return { isOpened: isEditorPanelOpened( PANEL_NAME ), isEnabled: isEditorPanelEnabled( PANEL_NAME ), + postType: getCurrentPostType(), }; }, [] ); @@ -36,9 +55,20 @@ function ExcerptPanel() { return null; } + // There are special cases where we want to label the excerpt as a description. + const shouldUseDescriptionLabel = [ + 'wp_template', + 'wp_template_part', + 'wp_block', + ].includes( postType ); + return ( @@ -61,3 +91,144 @@ export default function PostExcerptPanel() { ); } + +export function PrivatePostExcerptPanel() { + return ( + + + + ); +} + +function PrivateExcerpt() { + const { shouldRender, excerpt, shouldBeUsedAsDescription, allowEditing } = + useSelect( ( select ) => { + const { + getCurrentPostType, + getCurrentPostId, + getEditedPostAttribute, + isEditorPanelEnabled, + } = select( editorStore ); + const postType = getCurrentPostType(); + const isTemplateOrTemplatePart = [ + 'wp_template', + 'wp_template_part', + ].includes( postType ); + const isPattern = postType === 'wp_block'; + // These post types use the `excerpt` field as a description semantically, so we need to + // handle proper labeling and some flows where we should always render them as text. + const _shouldBeUsedAsDescription = + isTemplateOrTemplatePart || isPattern; + const _usedAttribute = isTemplateOrTemplatePart + ? 'description' + : 'excerpt'; + // We need to fetch the entity in this case to check if we'll allow editing. + const template = + isTemplateOrTemplatePart && + select( coreStore ).getEntityRecord( + 'postType', + postType, + getCurrentPostId() + ); + // For post types that use excerpt as description, we do not abide + // by the `isEnabled` panel flag in order to render them as text. + const _shouldRender = + isEditorPanelEnabled( PANEL_NAME ) || + _shouldBeUsedAsDescription; + return { + excerpt: getEditedPostAttribute( _usedAttribute ), + shouldRender: _shouldRender, + shouldBeUsedAsDescription: _shouldBeUsedAsDescription, + // If we should render, allow editing for all post types that are not used as description. + // For the rest allow editing only for user generated entities. + allowEditing: + _shouldRender && + ( ! _shouldBeUsedAsDescription || + isPattern || + ( template && + template.source === TEMPLATE_ORIGINS.custom && + ! template.has_theme_file ) ), + }; + }, [] ); + const [ popoverAnchor, setPopoverAnchor ] = useState( null ); + const label = shouldBeUsedAsDescription + ? __( 'Description' ) + : __( 'Excerpt' ); + // Memoize popoverProps to avoid returning a new object every time. + const popoverProps = useMemo( + () => ( { + // Anchor the popover to the middle of the entire row so that it doesn't + // move around when the label changes. + anchor: popoverAnchor, + 'aria-label': label, + headerTitle: label, + placement: 'left-start', + offset: 36, + shift: true, + } ), + [ popoverAnchor, label ] + ); + if ( ! shouldRender ) { + return false; + } + const excerptText = !! excerpt && ( + + { excerpt } + + ); + if ( ! allowEditing ) { + return excerptText; + } + const excerptPlaceholder = shouldBeUsedAsDescription + ? __( 'Add a description…' ) + : __( 'Add an excerpt…' ); + const triggerEditLabel = shouldBeUsedAsDescription + ? __( 'Edit description' ) + : __( 'Edit excerpt' ); + return ( + ( + + ) } + renderContent={ ( { onClose } ) => ( + <> + + + + + { ( fills ) => ( + <> + + { fills } + + ) } + + + + ) } + /> + ); +} diff --git a/packages/editor/src/components/post-excerpt/style.scss b/packages/editor/src/components/post-excerpt/style.scss index 056d81ad36c731..f5499c3423d19f 100644 --- a/packages/editor/src/components/post-excerpt/style.scss +++ b/packages/editor/src/components/post-excerpt/style.scss @@ -2,3 +2,27 @@ width: 100%; margin-bottom: 10px; } + +.editor-post-excerpt__dropdown__trigger { + height: auto; + padding: 0; + + &:not(.has-excerpt) { + color: $gray-700; + } + + &:hover { + color: $gray-900; + } +} + +.editor-post-excerpt__dropdown { + display: block; +} + +.editor-post-excerpt__dropdown__content { + .components-popover__content { + min-width: 320px; + padding: $grid-unit-20; + } +} diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index aae3762794b4d6..d031e1364e6a47 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -28,6 +28,7 @@ import PostCardPanel from './components/post-card-panel'; import PostStatus from './components/post-status'; import ToolsMoreMenuGroup from './components/more-menu/tools-more-menu-group'; import ViewMoreMenuGroup from './components/more-menu/view-more-menu-group'; +import { PrivatePostExcerptPanel } from './components/post-excerpt/panel'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -52,6 +53,7 @@ lock( privateApis, { PostStatus, ToolsMoreMenuGroup, ViewMoreMenuGroup, + PrivatePostExcerptPanel, // This is a temporary private API while we're updating the site editor to use EditorProvider. useAutoSwitchEditorSidebars, diff --git a/test/e2e/specs/editor/plugins/meta-boxes.spec.js b/test/e2e/specs/editor/plugins/meta-boxes.spec.js index 5999dc07aeafe0..1b7adc18760ff8 100644 --- a/test/e2e/specs/editor/plugins/meta-boxes.spec.js +++ b/test/e2e/specs/editor/plugins/meta-boxes.spec.js @@ -95,21 +95,12 @@ test.describe( 'Meta boxes', () => { .getByRole( 'textbox', { name: 'Add title' } ) .fill( 'A published post' ); - const documentSettings = page.getByRole( 'region', { - name: 'Editor settings', + const excerptButton = page.getByRole( 'button', { + name: 'Add an excerpt…', } ); - const excerptButton = documentSettings.getByRole( 'button', { - name: 'Excerpt', - } ); - - // eslint-disable-next-line playwright/no-conditional-in-test - if ( - ( await excerptButton.getAttribute( 'aria-expanded' ) ) === 'false' - ) { - await excerptButton.click(); - } + await excerptButton.click(); - await documentSettings + await page .getByRole( 'textbox', { name: 'Write an Excerpt' } ) .fill( 'Explicitly set excerpt.' ); diff --git a/test/e2e/specs/editor/various/new-post-default-content.spec.js b/test/e2e/specs/editor/various/new-post-default-content.spec.js index db9e3c38dc2962..6495cb3da012ef 100644 --- a/test/e2e/specs/editor/various/new-post-default-content.spec.js +++ b/test/e2e/specs/editor/various/new-post-default-content.spec.js @@ -23,8 +23,6 @@ test.describe( 'new editor filtered state', () => { test( 'should respect default content', async ( { editor, page } ) => { await editor.openDocumentSettingsSidebar(); - await page.click( 'role=button[name="Excerpt"i]' ); - // Assert they match what the plugin set. await expect( editor.canvas.locator( 'role=textbox[name="Add title"i]' ) @@ -33,7 +31,7 @@ test.describe( 'new editor filtered state', () => { .poll( editor.getEditedPostContent ) .toBe( 'My default content' ); await expect( - page.locator( 'role=textbox[name="Write an excerpt (optional)"i]' ) + page.getByRole( 'button', { name: 'Edit excerpt' } ) ).toHaveText( 'My default excerpt' ); } ); } ); diff --git a/test/e2e/specs/editor/various/sidebar.spec.js b/test/e2e/specs/editor/various/sidebar.spec.js index 9c332b192f41ae..465f37ea9625b3 100644 --- a/test/e2e/specs/editor/various/sidebar.spec.js +++ b/test/e2e/specs/editor/various/sidebar.spec.js @@ -115,9 +115,17 @@ test.describe( 'Sidebar', () => { 'Summary', 'Categories', 'Tags', - 'Excerpt', 'Discussion', ] ); + // Also check 'panels' that are not rendered as TabPanels. + const postExcerptPanel = page.getByRole( 'button', { + name: 'Add an excerpt…', + } ); + const postFeaturedImagePanel = page.getByRole( 'button', { + name: 'Set featured image', + } ); + await expect( postExcerptPanel ).toHaveCount( 1 ); + await expect( postFeaturedImagePanel ).toHaveCount( 1 ); await page.evaluate( () => { const { removeEditorPanel } = @@ -132,5 +140,7 @@ test.describe( 'Sidebar', () => { } ); await expect( documentSettingsPanels ).toHaveCount( 1 ); + await expect( postExcerptPanel ).toHaveCount( 0 ); + await expect( postFeaturedImagePanel ).toHaveCount( 0 ); } ); } ); From e82d0763f6a57f359d36354e78474a5dafb36777 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Thu, 25 Apr 2024 11:54:04 +0200 Subject: [PATCH 02/97] [Mobile] - KeyboardAwareFlatList - Enable FlatList virtualization for iOS (#59833) * Mobile - KeyboardAwareFlatList - Enable virtualization and optimization for larger lists * Mobile - KeyboardAwareFlatList - Unitfy optimization props for both platforms * Keyboard Aware FlatList - Unify props * BlockList - Update extraScrollHeight value and remove unneeded scroll style * RichText - Clear block selection when a block is unmounted * Fix import * RichText - Fix clearing current selection when a block is unmounted * Only clear selection if an AztecView is currently focused * RichText - Use isFocused instead of calling InputState * Fix clipping issue on Android * Update FlatList optimization props * Update Changelog * KeyboardAwareFlatList - Add docs for shared optimization properties Co-authored-by: geriux Co-authored-by: derekblank Co-authored-by: twstokes Co-authored-by: fluiddot --- .../src/components/block-list/index.native.js | 7 +-- .../src/components/rich-text/index.native.js | 11 +++++ .../rich-text/native/index.native.js | 11 +++++ .../keyboard-aware-flat-list/index.android.js | 13 ++++- .../keyboard-aware-flat-list/index.ios.js | 49 ++++++++++++------- .../keyboard-aware-flat-list/shared.native.js | 26 ++++++++++ .../styles.native.scss | 8 +++ packages/react-native-editor/CHANGELOG.md | 1 + 8 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js create mode 100644 packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c531ea8db4893d..830b7ffec02f4d 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -211,7 +211,7 @@ export default function BlockList( { ); }; - const { blockToolbar, headerToolbar, floatingToolbar } = styles; + const { blockToolbar, floatingToolbar } = styles; const containerStyle = { flex: isRootList ? 1 : 0, @@ -224,7 +224,6 @@ export default function BlockList( { const isMultiBlocks = blockClientIds.length > 1; const { isWider } = alignmentHelpers; const extraScrollHeight = - headerToolbar.height + blockToolbar.height + ( isFloatingToolbarVisible ? floatingToolbar.height : 0 ); @@ -245,14 +244,10 @@ export default function BlockList( { { ( { onScroll } ) => ( { + if ( getSelectedBlockClientId() === clientId ) { + clearSelectedBlock(); + } + }, [ clearSelectedBlock, clientId, getSelectedBlockClientId ] ); + const onDelete = useCallback( ( { value, isReverse } ) => { if ( onMerge ) { @@ -590,6 +600,7 @@ export function RichTextWrapper( disableSuggestions={ disableSuggestions } disableAutocorrection={ disableAutocorrection } containerWidth={ containerWidth } + clearCurrentSelectionOnUnmount={ clearCurrentSelectionOnUnmount } // Props to be set on the editable container are destructured on the // element itself for web (see below), but passed through rich text // for native. diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 8b4c871bda7bbf..26d39a0c6058b4 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -873,6 +873,17 @@ export class RichText extends Component { } } + componentWillUnmount() { + const { clearCurrentSelectionOnUnmount } = this.props; + + // There are cases when the component is unmounted e.g. scrolling in a + // long post due to virtualization, so the block selection needs to be cleared + // so it doesn't auto-focus when it's added back. + if ( this._editor?.isFocused() ) { + clearCurrentSelectionOnUnmount?.(); + } + } + getHtmlToRender( record, tagName ) { // Save back to HTML from React tree. let value = this.valueToFormat( record ); diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js index e66a0ffc28b542..0e92bef7df25f2 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -18,8 +18,10 @@ import { */ import useScroll from './use-scroll'; import KeyboardAvoidingView from '../keyboard-avoiding-view'; +import { OPTIMIZATION_ITEMS_THRESHOLD, OPTIMIZATION_PROPS } from './shared'; const AnimatedFlatList = Animated.createAnimatedComponent( FlatList ); +const EMPTY_OBJECT = {}; export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => { const { extraScrollHeight, scrollEnabled, shouldPreventAutomaticScroll } = @@ -41,8 +43,6 @@ export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => { const getFlatListRef = useCallback( ( flatListRef ) => { - // On Android, we get the ref of the associated scroll - // view to the FlatList. scrollViewRef.current = flatListRef?.getNativeScrollRef(); }, [ scrollViewRef ] @@ -57,12 +57,21 @@ export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => { }; } ); + const optimizationProps = + props.data?.length > OPTIMIZATION_ITEMS_THRESHOLD + ? OPTIMIZATION_PROPS + : EMPTY_OBJECT; + return ( diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index ac2c89188cbf68..9c224405a14a82 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -1,8 +1,7 @@ /** * External dependencies */ - -import { ScrollView, FlatList } from 'react-native'; +import { FlatList, View } from 'react-native'; import Animated from 'react-native-reanimated'; /** @@ -22,9 +21,12 @@ import { useThrottle } from '@wordpress/compose'; import useScroll from './use-scroll'; import useTextInputOffset from './use-text-input-offset'; import useTextInputCaretPosition from './use-text-input-caret-position'; +import { OPTIMIZATION_ITEMS_THRESHOLD, OPTIMIZATION_PROPS } from './shared'; +import styles from './styles.scss'; const DEFAULT_FONT_SIZE = 16; -const AnimatedScrollView = Animated.createAnimatedComponent( ScrollView ); +const AnimatedFlatList = Animated.createAnimatedComponent( FlatList ); +const EMPTY_OBJECT = {}; /** @typedef {import('@wordpress/element').RefObject} RefObject */ /** @@ -35,7 +37,6 @@ const AnimatedScrollView = Animated.createAnimatedComponent( ScrollView ); * @param {number} props.extraScrollHeight Extra scroll height for the content. * @param {Function} props.onScroll Function to be called when the list is scrolled. * @param {boolean} props.scrollEnabled Whether the list can be scrolled. - * @param {Object} props.scrollViewStyle Additional style for the ScrollView component. * @param {boolean} props.shouldPreventAutomaticScroll Whether to prevent scrolling when there's a Keyboard offset set. * @param {Object} props... Other props to pass to the FlatList component. * @param {RefObject} ref @@ -46,7 +47,6 @@ export const KeyboardAwareFlatList = ( extraScrollHeight, onScroll, scrollEnabled, - scrollViewStyle, shouldPreventAutomaticScroll, ...props }, @@ -105,7 +105,12 @@ export const KeyboardAwareFlatList = ( // extra padding at the bottom. const contentInset = { bottom: keyboardOffset }; - const style = [ { flex: 1 }, scrollViewStyle ]; + const getFlatListRef = useCallback( + ( flatListRef ) => { + scrollViewRef.current = flatListRef?.getNativeScrollRef(); + }, + [ scrollViewRef ] + ); useImperativeHandle( ref, () => { return { @@ -116,20 +121,26 @@ export const KeyboardAwareFlatList = ( }; } ); + const optimizationProps = + props.data?.length > OPTIMIZATION_ITEMS_THRESHOLD + ? OPTIMIZATION_PROPS + : EMPTY_OBJECT; + return ( - - - + + + ); }; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js b/packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js new file mode 100644 index 00000000000000..170ea77773ee1d --- /dev/null +++ b/packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js @@ -0,0 +1,26 @@ +/** + * Optimization properties for FlatList. + * @typedef {Object} OptimizationProps + * @property {number} maxToRenderPerBatch - Controls the amount of items rendered per batch during scrolling. + * Increasing this number reduces visual blank areas but may affect responsiveness. + * Default: 10 + * @property {number} windowSize - Measurement unit representing viewport height. + * Default: 21 (10 viewports above, 10 below, and 1 in between). + * Larger values reduce chances of seeing blank spaces while scrolling but increase memory consumption. + * Smaller values save memory but increase chances of seeing blank areas. + */ + +/** + * Threshold for applying optimization settings. + * @type {number} + */ +export const OPTIMIZATION_ITEMS_THRESHOLD = 30; + +/** + * Optimization properties for FlatList. + * @type {OptimizationProps} + */ +export const OPTIMIZATION_PROPS = { + maxToRenderPerBatch: 15, + windowSize: 17, +}; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss b/packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss new file mode 100644 index 00000000000000..bbde37bac1c37b --- /dev/null +++ b/packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss @@ -0,0 +1,8 @@ +.list__container { + flex-grow: 1; + align-items: stretch; +} + +.list__content { + margin-bottom: $mobile-block-toolbar-height; +} diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 8f4fcc63edd0fa..8381d09b04eb9d 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [*] Fix a crash when pasting file images and special comment markup [#60476] - [*] Update Aztec to v2.1.2 [#61007] +- [*] KeyboardAwareFlatList - Enable FlatList virtualization for iOS [#59833] ## 1.117.0 - [*] Add empty fallback option for the BottomSheetSelectControl component [#60333] From 64f9d9d1ced7a5aa7f3874890306554c5b703ce6 Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+sunil25393@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:32:40 +0530 Subject: [PATCH 03/97] Add documentation for EditorHistoryRedo and EditorHistoryUndo (#60932) Co-authored-by: sunil25393 Co-authored-by: ntsekouras --- packages/editor/README.md | 22 +++++++++++++++++-- .../src/components/editor-history/redo.js | 10 +++++++++ .../src/components/editor-history/undo.js | 10 +++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index 13ee575cecdddc..31c7f5a6bb8b2a 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -246,11 +246,29 @@ Undocumented declaration. ### EditorHistoryRedo -Undocumented declaration. +Renders the redo button for the editor history. + +_Parameters_ + +- _props_ `Object`: - Props. +- _ref_ `Ref`: - Forwarded ref. + +_Returns_ + +- `Component`: The component to be rendered. ### EditorHistoryUndo -Undocumented declaration. +Renders the undo button for the editor history. + +_Parameters_ + +- _props_ `Object`: - Props. +- _ref_ `Ref`: - Forwarded ref. + +_Returns_ + +- `Component`: The component to be rendered. ### EditorKeyboardShortcuts diff --git a/packages/editor/src/components/editor-history/redo.js b/packages/editor/src/components/editor-history/redo.js index 1383360a94bf1e..2ded5bfd52539a 100644 --- a/packages/editor/src/components/editor-history/redo.js +++ b/packages/editor/src/components/editor-history/redo.js @@ -41,4 +41,14 @@ function EditorHistoryRedo( props, ref ) { ); } +/** @typedef {import('react').Ref} Ref */ + +/** + * Renders the redo button for the editor history. + * + * @param {Object} props - Props. + * @param {Ref} ref - Forwarded ref. + * + * @return {Component} The component to be rendered. + */ export default forwardRef( EditorHistoryRedo ); diff --git a/packages/editor/src/components/editor-history/undo.js b/packages/editor/src/components/editor-history/undo.js index b13eb3162b9623..18f2cb2e7e35d6 100644 --- a/packages/editor/src/components/editor-history/undo.js +++ b/packages/editor/src/components/editor-history/undo.js @@ -37,4 +37,14 @@ function EditorHistoryUndo( props, ref ) { ); } +/** @typedef {import('react').Ref} Ref */ + +/** + * Renders the undo button for the editor history. + * + * @param {Object} props - Props. + * @param {Ref} ref - Forwarded ref. + * + * @return {Component} The component to be rendered. + */ export default forwardRef( EditorHistoryUndo ); From 867aeed85dd75d9add5b9eed13fd50f54e269e16 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Thu, 25 Apr 2024 16:57:51 +0530 Subject: [PATCH 04/97] Site editor: fix typo in template actions string (#61089) Co-authored-by: itzmekhokan Co-authored-by: ellatrix --- packages/edit-site/src/components/template-actions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/template-actions/index.js b/packages/edit-site/src/components/template-actions/index.js index 06a644f79c29e0..c5bbe553bb9fb2 100644 --- a/packages/edit-site/src/components/template-actions/index.js +++ b/packages/edit-site/src/components/template-actions/index.js @@ -135,7 +135,7 @@ function ResetMenuItem( { template, onClose } ) { onCancel={ () => setIsModalOpen( false ) } confirmButtonText={ __( 'Reset' ) } > - { __( 'Rese to default and clear all customizations?' ) } + { __( 'Reset to default and clear all customizations?' ) } ); From a1b07cd115b9773b310bb99ed99fa1eaa944c503 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 25 Apr 2024 15:36:33 +0400 Subject: [PATCH 05/97] ListViewBlock: Combine 'useSelect' hooks, part two (#61054) Co-authored-by: Mamaduka Co-authored-by: tyxla --- .../src/components/list-view/block.js | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 930d0a0f80ef66..658188dbd0fc70 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -82,25 +82,26 @@ function ListViewBlock( { const blockInformation = useBlockDisplayInformation( clientId ); - const { block, blockName, blockEditingMode } = useSelect( - ( select ) => { - const { getBlock, getBlockName, getBlockEditingMode } = - select( blockEditorStore ); - - return { - block: getBlock( clientId ), - blockName: getBlockName( clientId ), - blockEditingMode: getBlockEditingMode( clientId ), - }; - }, - [ clientId ] - ); - - const allowRightClickOverrides = useSelect( - ( select ) => - select( blockEditorStore ).getSettings().allowRightClickOverrides, - [] - ); + const { block, blockName, blockEditingMode, allowRightClickOverrides } = + useSelect( + ( select ) => { + const { + getBlock, + getBlockName, + getBlockEditingMode, + getSettings, + } = select( blockEditorStore ); + + return { + block: getBlock( clientId ), + blockName: getBlockName( clientId ), + blockEditingMode: getBlockEditingMode( clientId ), + allowRightClickOverrides: + getSettings().allowRightClickOverrides, + }; + }, + [ clientId ] + ); const showBlockActions = // When a block hides its toolbar it also hides the block settings menu, From 470f11e4cc7af408689a53571e5fe10e82fdf8fa Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:35:12 +0200 Subject: [PATCH 06/97] useBlockSync: avoid replacing blocks twice on mount (#60967) Co-authored-by: ellatrix Co-authored-by: jsnajdr Co-authored-by: youknowriad Co-authored-by: tyxla --- .../src/components/provider/test/use-block-sync.js | 7 +++++++ .../src/components/provider/use-block-sync.js | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/block-editor/src/components/provider/test/use-block-sync.js b/packages/block-editor/src/components/provider/test/use-block-sync.js index 9b16e966249fa7..aae5e517c63029 100644 --- a/packages/block-editor/src/components/provider/test/use-block-sync.js +++ b/packages/block-editor/src/components/provider/test/use-block-sync.js @@ -71,6 +71,7 @@ describe( 'useBlockSync hook', () => { expect( onInput ).not.toHaveBeenCalled(); expect( replaceInnerBlocks ).not.toHaveBeenCalled(); expect( resetBlocks ).toHaveBeenCalledWith( fakeBlocks ); + expect( resetBlocks ).toHaveBeenCalledTimes( 1 ); const testBlocks = [ { clientId: 'a', innerBlocks: [], attributes: { foo: 1 } }, @@ -88,6 +89,7 @@ describe( 'useBlockSync hook', () => { expect( onInput ).not.toHaveBeenCalled(); expect( replaceInnerBlocks ).not.toHaveBeenCalled(); expect( resetBlocks ).toHaveBeenCalledWith( testBlocks ); + expect( resetBlocks ).toHaveBeenCalledTimes( 2 ); unmount(); @@ -95,6 +97,7 @@ describe( 'useBlockSync hook', () => { expect( onInput ).not.toHaveBeenCalled(); expect( replaceInnerBlocks ).not.toHaveBeenCalled(); expect( resetBlocks ).toHaveBeenCalledWith( [] ); + expect( resetBlocks ).toHaveBeenCalledTimes( 3 ); } ); it( 'replaces the inner blocks of a block when the controlled value changes if a clientId is passed', async () => { @@ -123,6 +126,7 @@ describe( 'useBlockSync hook', () => { 'test', // It should use the given client ID. fakeBlocks // It should use the controlled blocks value. ); + expect( replaceInnerBlocks ).toHaveBeenCalledTimes( 1 ); const testBlocks = [ { @@ -148,6 +152,7 @@ describe( 'useBlockSync hook', () => { expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', [ expect.objectContaining( { name: 'test/test-block' } ), ] ); + expect( replaceInnerBlocks ).toHaveBeenCalledTimes( 2 ); unmount(); @@ -155,6 +160,7 @@ describe( 'useBlockSync hook', () => { expect( onInput ).not.toHaveBeenCalled(); expect( resetBlocks ).not.toHaveBeenCalled(); expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', [] ); + expect( replaceInnerBlocks ).toHaveBeenCalledTimes( 3 ); } ); it( 'does not add the controlled blocks to the block-editor store if the store already contains them', async () => { @@ -354,6 +360,7 @@ describe( 'useBlockSync hook', () => { ); expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', [] ); + expect( replaceInnerBlocks ).toHaveBeenCalledTimes( 1 ); expect( onChange ).not.toHaveBeenCalled(); expect( onInput ).not.toHaveBeenCalled(); } ); diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index 969c0f1e4d1c5e..7dfbd6a8f320e9 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -186,7 +186,15 @@ export default function useBlockSync( { } }, [ controlledBlocks, clientId ] ); + const isMounted = useRef( false ); + useEffect( () => { + // On mount, controlled blocks are already set in the effect above. + if ( ! isMounted.current ) { + isMounted.current = true; + return; + } + // When the block becomes uncontrolled, it means its inner state has been reset // we need to take the blocks again from the external value property. if ( ! isControlled ) { From 9cc96a7b81cc186e1ec67c40c89583aae0b334f4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 25 Apr 2024 15:32:09 +0100 Subject: [PATCH 07/97] Editor: Avoid triggering the start page modal on unsaved pages (#61082) Co-authored-by: youknowriad Co-authored-by: richtabor --- packages/editor/src/components/start-page-options/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/start-page-options/index.js b/packages/editor/src/components/start-page-options/index.js index 70e7ecd642d4c5..891cb5ca209e72 100644 --- a/packages/editor/src/components/start-page-options/index.js +++ b/packages/editor/src/components/start-page-options/index.js @@ -11,6 +11,7 @@ import { import { useSelect, useDispatch } from '@wordpress/data'; import { useAsyncList } from '@wordpress/compose'; import { store as coreStore } from '@wordpress/core-data'; +import { __unstableSerializeAndClean } from '@wordpress/blocks'; /** * Internal dependencies @@ -71,7 +72,11 @@ function PatternSelection( { blockPatterns, onChoosePattern } ) { blockPatterns={ blockPatterns } shownPatterns={ shownBlockPatterns } onClickPattern={ ( _pattern, blocks ) => { - editEntityRecord( 'postType', postType, postId, { blocks } ); + editEntityRecord( 'postType', postType, postId, { + blocks, + content: ( { blocks: blocksForSerialization = [] } ) => + __unstableSerializeAndClean( blocksForSerialization ), + } ); onChoosePattern(); } } /> From 41c80e8551294914681b060caded03cfe563c6b6 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 25 Apr 2024 18:47:54 +0200 Subject: [PATCH 08/97] docgen: fix qualified types with type parameters (#61097) Co-authored-by: jsnajdr Co-authored-by: ellatrix Co-authored-by: Mamaduka Co-authored-by: tyxla --- docs/reference-guides/data/data-core.md | 6 +++--- packages/core-data/README.md | 6 +++--- packages/docgen/lib/get-type-annotation.js | 22 ++++++++++++--------- packages/docgen/test/get-type-annotation.js | 20 +++++++++++++++++++ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index e740998a236233..88db705f23f122 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -150,7 +150,7 @@ _Parameters_ _Returns_ -- `undefined< 'edit' >`: Current user object. +- `ET.User< 'edit' >`: Current user object. ### getDefaultTemplateId @@ -178,7 +178,7 @@ _Parameters_ _Returns_ -- `undefined< EntityRecord > | false`: The entity record, merged with its edits. +- `ET.Updatable< EntityRecord > | false`: The entity record, merged with its edits. ### getEmbedPreview @@ -504,7 +504,7 @@ _Parameters_ _Returns_ -- `undefined< 'edit' >[]`: Users list. +- `ET.User< 'edit' >[]`: Users list. ### hasEditsForEntityRecord diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 444bce35ee5218..20ed0d4c660e2f 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -471,7 +471,7 @@ _Parameters_ _Returns_ -- `undefined< 'edit' >`: Current user object. +- `ET.User< 'edit' >`: Current user object. ### getDefaultTemplateId @@ -499,7 +499,7 @@ _Parameters_ _Returns_ -- `undefined< EntityRecord > | false`: The entity record, merged with its edits. +- `ET.Updatable< EntityRecord > | false`: The entity record, merged with its edits. ### getEmbedPreview @@ -825,7 +825,7 @@ _Parameters_ _Returns_ -- `undefined< 'edit' >[]`: Users list. +- `ET.User< 'edit' >[]`: Users list. ### hasEditsForEntityRecord diff --git a/packages/docgen/lib/get-type-annotation.js b/packages/docgen/lib/get-type-annotation.js index b0a920cefce031..ea854e13984fbe 100644 --- a/packages/docgen/lib/get-type-annotation.js +++ b/packages/docgen/lib/get-type-annotation.js @@ -237,16 +237,20 @@ function getMappedTypeAnnotation( typeAnnotation ) { * @param {babelTypes.TSTypeReference} typeAnnotation */ function getTypeReferenceTypeAnnotation( typeAnnotation ) { - if ( ! typeAnnotation.typeParameters ) { - if ( babelTypes.isTSQualifiedName( typeAnnotation.typeName ) ) { - return unifyQualifiedName( typeAnnotation.typeName ); - } - return typeAnnotation.typeName.name; + let typeName; + if ( babelTypes.isTSQualifiedName( typeAnnotation.typeName ) ) { + typeName = unifyQualifiedName( typeAnnotation.typeName ); + } else { + typeName = typeAnnotation.typeName.name; } - const typeParams = typeAnnotation.typeParameters.params - .map( getTypeAnnotation ) - .join( ', ' ); - return `${ typeAnnotation.typeName.name }< ${ typeParams } >`; + + if ( typeAnnotation.typeParameters ) { + const typeParams = typeAnnotation.typeParameters.params + .map( getTypeAnnotation ) + .join( ', ' ); + typeName = `${ typeName }< ${ typeParams } >`; + } + return typeName; } /** diff --git a/packages/docgen/test/get-type-annotation.js b/packages/docgen/test/get-type-annotation.js index fdf114dfd1f674..e9fc493909ceb8 100644 --- a/packages/docgen/test/get-type-annotation.js +++ b/packages/docgen/test/get-type-annotation.js @@ -100,6 +100,26 @@ describe( 'Type annotations', () => { } ); } ); + describe( 'qualified types', () => { + const node = parse( ` + function fn( foo: My.Foo< string >, bar: My.Bar ) { + return 0; + } + ` ); + + it( 'should get the qualified param type with type parameters', () => { + expect( + getTypeAnnotation( { tag: 'param', name: 'foo' }, node, 0 ) + ).toBe( 'My.Foo< string >' ); + } ); + + it( 'should get the qualified param type without type parameters', () => { + expect( + getTypeAnnotation( { tag: 'param', name: 'bar' }, node, 1 ) + ).toBe( 'My.Bar' ); + } ); + } ); + describe( 'literal values', () => { it.each( [ "'a-string-literal'", '1000n', 'true', '1000' ] )( 'should handle %s', From 2012a36da2a7752dceeda3dcf0af8e58985e8072 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 25 Apr 2024 12:57:52 -0500 Subject: [PATCH 09/97] Add defaultFontSizes theme.json (v2) (#58409) Co-authored-by: ajlende Co-authored-by: draganescu Co-authored-by: scruffian Co-authored-by: gaambo Co-authored-by: youknowriad Co-authored-by: oandregal --- docs/manifest.json | 2 +- .../theme-json-reference/theme-json-living.md | 12 +- .../theme-json-migrations.md | 24 ++ .../theme-json-reference/theme-json-v2.md | 336 ++++++++++++++++++ lib/class-wp-theme-json-data-gutenberg.php | 2 +- lib/class-wp-theme-json-gutenberg.php | 68 ++-- ...class-wp-theme-json-resolver-gutenberg.php | 13 +- lib/class-wp-theme-json-schema-gutenberg.php | 61 +++- lib/theme.json | 3 +- .../src/components/global-styles/hooks.js | 2 + .../global-styles/typography-panel.js | 38 +- packages/block-editor/src/hooks/utils.js | 4 + packages/block-editor/src/utils/object.js | 16 + ...lobal-styles-controller-gutenberg-test.php | 2 +- phpunit/class-wp-theme-json-schema-test.php | 82 ++++- phpunit/class-wp-theme-json-test.php | 82 ++--- .../theme.json | 2 +- .../theme.json | 2 +- .../theme.json | 2 +- .../block-theme-child/styles/variation-a.json | 2 +- .../block-theme-child/styles/variation-b.json | 2 +- .../themedir1/block-theme-child/theme.json | 2 +- .../block-theme/styles/variation-a.json | 2 +- .../block-theme/styles/variation-b.json | 2 +- .../block-theme/styles/variation.json | 2 +- phpunit/data/themedir1/block-theme/theme.json | 2 +- .../styles/variation-duplicate-fonts.json | 2 +- .../styles/variation-new-font-family.json | 2 +- .../styles/variation-new-font-variations.json | 2 +- .../styles/variation-no-fonts.json | 2 +- .../themedir1/fonts-block-theme/theme.json | 2 +- schemas/json/theme.json | 7 +- test/emptytheme/styles/variation.json | 2 +- test/emptytheme/theme.json | 2 +- .../style-variations/styles/pink.json | 2 +- .../style-variations/styles/yellow.json | 2 +- .../style-variations/theme.json | 2 +- 37 files changed, 656 insertions(+), 138 deletions(-) create mode 100644 docs/reference-guides/theme-json-reference/theme-json-v2.md diff --git a/docs/manifest.json b/docs/manifest.json index cf30aff19c503b..df12afa59cbc5a 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -564,7 +564,7 @@ "parent": "reference-guides" }, { - "title": "Theme.json Version 2", + "title": "Theme.json Version 3", "slug": "theme-json-living", "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-living.md", "parent": "theme-json-reference" diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 9120af4f7456c6..bea655e61af827 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -1,10 +1,11 @@ -# Theme.json Version 2 +# Theme.json Version 3 -> This is the living specification for **version 2** of `theme.json`. This version works with WordPress 5.9 or later, and the latest Gutenberg plugin. +> This is the living specification for **version 3** of `theme.json`. This version works with WordPress 5.9 or later, and the latest Gutenberg plugin. > > There are some related documents that you may be interested in: -> - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, and -> - the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). +> - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, +> - the [theme.json v2](/docs/reference-guides/theme-json-reference/theme-json-v2.md) specification, and +> - the [reference to migrate from older theme.json versions](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guidance on how to use the `theme.json` file in your theme. @@ -17,7 +18,7 @@ Code editors can pick up the schema and can provide helpful hints and suggestion ``` { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, ... } ``` @@ -179,6 +180,7 @@ Settings related to typography. | Property | Type | Default | Props | | --- | --- | --- |--- | +| defaultFontSizes | boolean | true | | | customFontSize | boolean | true | | | fontStyle | boolean | true | | | fontWeight | boolean | true | | diff --git a/docs/reference-guides/theme-json-reference/theme-json-migrations.md b/docs/reference-guides/theme-json-reference/theme-json-migrations.md index b043ca1fba52ac..07acf41bf2eb7f 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-migrations.md +++ b/docs/reference-guides/theme-json-reference/theme-json-migrations.md @@ -63,3 +63,27 @@ Additions to styles: ### Changes to property values The default font sizes provided by core (`settings.typography.fontSizes`) have been updated. The Normal and Huge sizes (with `normal` and `huge` slugs) have been removed from the list, and Extra Large (`x-large` slug) has been added. When the UI controls show the default values provided by core, Normal and Huge will no longer be present. However, their CSS classes and CSS Custom Properties are still enqueued to make sure existing content that uses them still works as expected. + +## Migrating from v2 to v3 + +Upgrading to v3 adjusts preset defaults to be more consistent with one another. + +### How to migrate from v1 to v2: + +1. Update `version` to `3`. +2. Configure the changed defaults. + +### Changed defaults + +#### `settings.typography.defaultFontSizes` + +In theme.json v2, the default font sizes were only shown when theme sizes were not defined. A theme providing font sizes with the same slugs as the defaults would always override them. + +The new `defaultFontSizes` option gives control over showing default font sizes and preventing those defaults from being overridden. + +- When set to `true` it will show the default font sizes and prevent them from being overridden by the theme. +- When set to `false` it will hide the default font sizes and allow the theme to use the default slugs. + +It is `true` by default when switching to v3. This is to be consistent with how other `default*` options work such as `settings.color.defaultPalette`. + +To keep behavior similar to v2, set this value to `false`. diff --git a/docs/reference-guides/theme-json-reference/theme-json-v2.md b/docs/reference-guides/theme-json-reference/theme-json-v2.md new file mode 100644 index 00000000000000..56cdff1c3bb863 --- /dev/null +++ b/docs/reference-guides/theme-json-reference/theme-json-v2.md @@ -0,0 +1,336 @@ +# Theme.json Version 2 + +> This is the living specification for **version 2** of `theme.json`. This version works with WordPress 5.9 or later, and the latest Gutenberg plugin. +> +> There are some related documents that you may be interested in: +> - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, and +> - the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). + +This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guidance on how to use the `theme.json` file in your theme. + +## Schema + +Remembering the `theme.json` settings and properties while you develop can be difficult, so a [JSON schema](https://schemas.wp.org/trunk/theme.json) was created to help. + +Code editors can pick up the schema and can provide helpful hints and suggestions such as tooltips, autocomplete, or schema validation in the editor. To use the schema in Visual Studio Code, add `$schema`: "https://schemas.wp.org/trunk/theme.json" to the beginning of your theme.json file together with a `version` corresponding to the version you wish to use, e.g.: + +``` +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2, + ... +} +``` + +## Settings + +### appearanceTools + +Setting that enables the following UI tools: + +- background: backgroundImage, backgroundSize +- border: color, radius, style, width +- color: link, heading, button, caption +- dimensions: aspectRatio, minHeight +- position: sticky +- spacing: blockGap, margin, padding +- typography: lineHeight + +--- + +### useRootPaddingAwareAlignments + +_**Note:** Since WordPress 6.1._ + +Enables root padding (the values from `styles.spacing.padding`) to be applied to the contents of full-width blocks instead of the root block. + +Please note that when using this setting, `styles.spacing.padding` should always be set as an object with `top`, `right`, `bottom`, `left` values declared separately. + +--- + +### border + +Settings related to borders. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| color | boolean | false | | +| radius | boolean | false | | +| style | boolean | false | | +| width | boolean | false | | + +--- + +### shadow + +Settings related to shadows. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| defaultPresets | boolean | true | | +| presets | array | | name, shadow, slug | + +--- + +### color + +Settings related to colors. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| background | boolean | true | | +| custom | boolean | true | | +| customDuotone | boolean | true | | +| customGradient | boolean | true | | +| defaultDuotone | boolean | true | | +| defaultGradients | boolean | true | | +| defaultPalette | boolean | true | | +| duotone | array | | colors, name, slug | +| gradients | array | | gradient, name, slug | +| link | boolean | false | | +| palette | array | | color, name, slug | +| text | boolean | true | | +| heading | boolean | true | | +| button | boolean | true | | +| caption | boolean | true | | + +--- + +### background + +Settings related to background. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| backgroundImage | boolean | false | | +| backgroundSize | boolean | false | | + +--- + +### dimensions + +Settings related to dimensions. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| aspectRatio | boolean | false | | +| minHeight | boolean | false | | + +--- + +### layout + +Settings related to layout. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| contentSize | string | | | +| wideSize | string | | | +| allowEditing | boolean | true | | +| allowCustomContentAndWideSize | boolean | true | | + +--- + +### lightbox + +Settings related to the lightbox. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| enabled | boolean | | | +| allowEditing | boolean | | | + +--- + +### position + +Settings related to position. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| sticky | boolean | false | | + +--- + +### spacing + +Settings related to spacing. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| blockGap | boolean, null | null | | +| margin | boolean | false | | +| padding | boolean | false | | +| units | array | px,em,rem,vh,vw,% | | +| customSpacingSize | boolean | true | | +| spacingSizes | array | | name, size, slug | +| spacingScale | object | | | + +--- + +### typography + +Settings related to typography. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| customFontSize | boolean | true | | +| fontStyle | boolean | true | | +| fontWeight | boolean | true | | +| fluid | object, boolean | false | _{maxViewportWidth, minFontSize, minViewportWidth}_ | +| letterSpacing | boolean | true | | +| lineHeight | boolean | false | | +| textAlign | boolean | true | | +| textColumns | boolean | false | | +| textDecoration | boolean | true | | +| writingMode | boolean | false | | +| textTransform | boolean | true | | +| dropCap | boolean | true | | +| fontSizes | array | | fluid, name, size, slug | +| fontFamilies | array | | fontFace, fontFamily, name, slug | + +--- + +### custom + +Generate custom CSS custom properties of the form `--wp--custom--{key}--{nested-key}: {value};`. `camelCased` keys are transformed to `kebab-case` as to follow the CSS property naming schema. Keys at different depth levels are separated by `--`, so keys should not include `--` in the name. + +--- + +## Styles + +### border + +Border styles. + +| Property | Type | Props | +| --- | --- |--- | +| color | string, object | | +| radius | string, object | | +| style | string, object | | +| width | string, object | | +| top | object | color, style, width | +| right | object | color, style, width | +| bottom | object | color, style, width | +| left | object | color, style, width | + +--- + +### color + +Color styles. + +| Property | Type | Props | +| --- | --- |--- | +| background | string, object | | +| gradient | string, object | | +| text | string, object | | + +--- + +### dimensions + +Dimensions styles + +| Property | Type | Props | +| --- | --- |--- | +| aspectRatio | string, object | | +| minHeight | string, object | | + +--- + +### spacing + +Spacing styles. + +| Property | Type | Props | +| --- | --- |--- | +| blockGap | string, object | | +| margin | object | bottom, left, right, top | +| padding | object | bottom, left, right, top | + +--- + +### typography + +Typography styles. + +| Property | Type | Props | +| --- | --- |--- | +| fontFamily | string, object | | +| fontSize | string, object | | +| fontStyle | string, object | | +| fontWeight | string, object | | +| letterSpacing | string, object | | +| lineHeight | string, object | | +| textAlign | string | | +| textColumns | string | | +| textDecoration | string, object | | +| writingMode | string, object | | +| textTransform | string, object | | + +--- + +### filter + +CSS and SVG filter styles. + +| Property | Type | Props | +| --- | --- |--- | +| duotone | string, object | | + +--- + +### shadow + +Box shadow styles. + +--- + +### outline + +Outline styles. + +| Property | Type | Props | +| --- | --- |--- | +| color | string, object | | +| offset | string, object | | +| style | string, object | | +| width | string, object | | + +--- + +### css + +Sets custom CSS to apply styling not covered by other theme.json properties. + +--- + +## customTemplates + +Additional metadata for custom templates defined in the templates folder. + +Type: `object`. + +| Property | Description | Type | +| --- | --- | --- | +| name | Filename, without extension, of the template in the templates folder. | string | +| title | Title of the template, translatable. | string | +| postTypes | List of post types that can use this custom template. | array | + +## templateParts + +Additional metadata for template parts defined in the parts folder. + +Type: `object`. + +| Property | Description | Type | +| --- | --- | --- | +| name | Filename, without extension, of the template in the parts folder. | string | +| title | Title of the template, translatable. | string | +| area | The area the template part is used for. Block variations for `header` and `footer` values exist and will be used when the area is set to one of those. | string | + +## Patterns + +An array of pattern slugs to be registered from the Pattern Directory. +Type: `array`. diff --git a/lib/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php index 6877a209b687f5..c564016b1a7119 100644 --- a/lib/class-wp-theme-json-data-gutenberg.php +++ b/lib/class-wp-theme-json-data-gutenberg.php @@ -38,7 +38,7 @@ class WP_Theme_JSON_Data_Gutenberg { * @param array $data Array following the theme.json specification. * @param string $origin The origin of the data: default, theme, user. */ - public function __construct( $data = array(), $origin = 'theme' ) { + public function __construct( $data = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) { $this->origin = $origin; $this->theme_json = new WP_Theme_JSON_Gutenberg( $data, $this->origin ); } diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1d3f8feb90e23e..d685c7bdc846dd 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -159,7 +159,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => false, + 'prevent_override' => array( 'typography', 'defaultFontSizes' ), 'use_default_names' => true, 'value_func' => 'gutenberg_get_typography_font_size_value', 'css_vars' => '--wp--preset--font-size--$slug', @@ -428,20 +428,21 @@ class WP_Theme_JSON_Gutenberg { 'defaultPresets' => null, ), 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textAlign' => null, - 'textColumns' => null, - 'textDecoration' => null, - 'textTransform' => null, - 'writingMode' => null, + 'fluid' => null, + 'customFontSize' => null, + 'defaultFontSizes' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textAlign' => null, + 'textColumns' => null, + 'textDecoration' => null, + 'textTransform' => null, + 'writingMode' => null, ), ); @@ -705,9 +706,10 @@ public static function get_element_class_name( $element ) { * * @since 5.8.0 * @since 5.9.0 Changed value from 1 to 2. + * @since 6.5.0 Changed value from 2 to 3. * @var int */ - const LATEST_SCHEMA = 2; + const LATEST_SCHEMA = 3; /** * Constructor. @@ -718,7 +720,7 @@ public static function get_element_class_name( $element ) { * @param string $origin Optional. What source of data this object represents. * One of 'default', 'theme', or 'custom'. Default 'theme'. */ - public function __construct( $theme_json = array(), $origin = 'theme' ) { + public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } @@ -2890,12 +2892,15 @@ public function merge( $incoming ) { } // Replace the presets. - foreach ( static::PRESETS_METADATA as $preset ) { - $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $prevent_override = $preset_metadata['prevent_override']; + if ( is_array( $prevent_override ) ) { + $prevent_override = _wp_array_get( $this->theme_json['settings'], $preset_metadata['prevent_override'] ); + } foreach ( static::VALID_ORIGINS as $origin ) { $base_path = $node['path']; - foreach ( $preset['path'] as $leaf ) { + foreach ( $preset_metadata['path'] as $leaf ) { $base_path[] = $leaf; } @@ -2907,7 +2912,8 @@ public function merge( $incoming ) { continue; } - if ( 'theme' === $origin && $preset['use_default_names'] ) { + // Set names for theme presets based on the slug if they are not set and can use default names. + if ( 'theme' === $origin && $preset_metadata['use_default_names'] ) { foreach ( $content as $key => $item ) { if ( ! isset( $item['name'] ) ) { $name = static::get_name_from_defaults( $item['slug'], $base_path ); @@ -2918,19 +2924,17 @@ public function merge( $incoming ) { } } - if ( - ( 'theme' !== $origin ) || - ( 'theme' === $origin && $override_preset ) - ) { - _wp_array_set( $this->theme_json, $path, $content ); - } else { - $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); - $slugs = array_merge_recursive( $slugs_global, $slugs_node ); + // Filter out default slugs from theme presets when defaults should not be overridden. + if ( 'theme' === $origin && $prevent_override ) { + $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); + $preset_global = _wp_array_get( $slugs_global, $preset_metadata['path'], array() ); + $preset_node = _wp_array_get( $slugs_node, $preset_metadata['path'], array() ); + $preset_slugs = array_merge_recursive( $preset_global, $preset_node ); - $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); - $content = static::filter_slugs( $content, $slugs_for_preset ); - _wp_array_set( $this->theme_json, $path, $content ); + $content = static::filter_slugs( $content, $preset_slugs ); } + + _wp_array_set( $this->theme_json, $path, $content ); } } } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 472e671ee8a264..dcc0bf8b099c3b 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -244,7 +244,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_json_data = static::read_json_file( $theme_json_file ); $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); } else { - $theme_json_data = array(); + $theme_json_data = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ); } /** @@ -375,7 +375,7 @@ public static function get_block_data() { return static::$blocks; } - $config = array( 'version' => 2 ); + $config = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ); foreach ( $blocks as $block_name => $block_type ) { if ( isset( $block_type->supports['__experimentalStyle'] ) ) { $config['styles']['blocks'][ $block_name ] = static::remove_json_comments( $block_type->supports['__experimentalStyle'] ); @@ -544,14 +544,17 @@ public static function get_user_data() { isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && $decoded_data['isGlobalStylesUserThemeJSON'] ) { - unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); $config = $decoded_data; } } /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ - $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); - $config = $theme_json->get_data(); + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); + $config = $theme_json->get_data(); + + // Needs to be set for schema migrations of user data. + $config['isGlobalStylesUserThemeJSON'] = true; + static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' ); return static::$user; diff --git a/lib/class-wp-theme-json-schema-gutenberg.php b/lib/class-wp-theme-json-schema-gutenberg.php index 8373e133c5aec8..553354c438ed4a 100644 --- a/lib/class-wp-theme-json-schema-gutenberg.php +++ b/lib/class-wp-theme-json-schema-gutenberg.php @@ -38,6 +38,7 @@ class WP_Theme_JSON_Schema_Gutenberg { * Function that migrates a given theme.json structure to the last version. * * @since 5.9.0 + * @since 6.5.0 Migrate up to v3. * * @param array $theme_json The structure to migrate. * @@ -50,8 +51,14 @@ public static function migrate( $theme_json ) { ); } - if ( 1 === $theme_json['version'] ) { - $theme_json = self::migrate_v1_to_v2( $theme_json ); + // Migrate each version in order starting with the current version. + switch ( $theme_json['version'] ) { + case 1: + $theme_json = self::migrate_v1_to_v2( $theme_json ); + // no break + case 2: + $theme_json = self::migrate_v2_to_v3( $theme_json ); + // no break } return $theme_json; @@ -87,6 +94,56 @@ private static function migrate_v1_to_v2( $old ) { return $new; } + /** + * Migrates from v2 to v3. + * + * - Sets settings.typography.defaultFontSizes to false. + * + * @since 6.5.0 + * + * @param array $old Data to migrate. + * + * @return array Data with defaultFontSizes set to false. + */ + private static function migrate_v2_to_v3( $old ) { + // Copy everything. + $new = $old; + + // Set the new version. + $new['version'] = 3; + + /* + * Remaining changes do not need to be applied to the custom origin, + * as they should take on the value of the theme origin. + */ + if ( + isset( $new['isGlobalStylesUserThemeJSON'] ) && + true === $new['isGlobalStylesUserThemeJSON'] + ) { + return $new; + } + + /* + * Even though defaultFontSizes is a new setting, we need to migrate + * it as it controls the PRESETS_METADATA prevent_override which was + * previously hardcoded to false. This only needs to happen when the + * theme provided font sizes as they could match the default ones and + * affect the generated CSS. And in v2 we provided default font sizes + * when the theme did not provide any. + */ + if ( isset( $new['settings']['typography']['fontSizes'] ) ) { + if ( ! isset( $new['settings'] ) ) { + $new['settings'] = array(); + } + if ( ! isset( $new['settings']['typography'] ) ) { + $new['settings']['typography'] = array(); + } + $new['settings']['typography']['defaultFontSizes'] = false; + } + + return $new; + } + /** * Processes the settings subtree. * diff --git a/lib/theme.json b/lib/theme.json index ece76b5f63cb29..574226f4741526 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": false, "useRootPaddingAwareAlignments": false, @@ -236,6 +236,7 @@ }, "typography": { "customFontSize": true, + "defaultFontSizes": true, "dropCap": true, "fontSizes": [ { diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index bdda9563edae02..e0de34cf2280e2 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -68,6 +68,7 @@ const VALID_SETTINGS = [ 'spacing.units', 'typography.fluid', 'typography.customFontSize', + 'typography.defaultFontSizes', 'typography.dropCap', 'typography.fontFamilies', 'typography.fontSizes', @@ -240,6 +241,7 @@ export function useSettingsForBlockElement( ...updatedSettings.typography, fontSizes: {}, customFontSize: false, + defaultFontSizes: false, }; } diff --git a/packages/block-editor/src/components/global-styles/typography-panel.js b/packages/block-editor/src/components/global-styles/typography-panel.js index e82804c9cc9d41..76836c775bb807 100644 --- a/packages/block-editor/src/components/global-styles/typography-panel.js +++ b/packages/block-editor/src/components/global-styles/typography-panel.js @@ -13,11 +13,7 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import { - mergeOrigins, - overrideOrigins, - hasOriginValue, -} from '../../store/get-block-settings'; +import { mergeOrigins, hasOriginValue } from '../../store/get-block-settings'; import FontFamilyControl from '../font-family'; import FontAppearanceControl from '../font-appearance-control'; import LineHeightControl from '../line-height-control'; @@ -57,7 +53,10 @@ export function useHasTypographyPanel( settings ) { function useHasFontSizeControl( settings ) { return ( - hasOriginValue( settings?.typography?.fontSizes ) || + ( settings?.typography?.defaultFontSizes !== false && + settings?.typography?.fontSizes?.default?.length ) || + settings?.typography?.fontSizes?.theme?.length || + settings?.typography?.fontSizes?.custom?.length || settings?.typography?.customFontSize ); } @@ -104,16 +103,21 @@ function useHasTextColumnsControl( settings ) { return settings?.typography?.textColumns; } -function getUniqueFontSizesBySlug( settings ) { - const fontSizes = settings?.typography?.fontSizes ?? {}; - const overriddenFontSizes = overrideOrigins( fontSizes ) ?? []; - const uniqueSizes = []; - for ( const currentSize of overriddenFontSizes ) { - if ( ! uniqueSizes.some( ( { slug } ) => slug === currentSize.slug ) ) { - uniqueSizes.push( currentSize ); - } - } - return uniqueSizes; +/** + * Concatenate all the font sizes into a single list for the font size picker. + * + * @param {Object} settings The global styles settings. + * + * @return {Array} The merged font sizes. + */ +function getMergedFontSizes( settings ) { + const fontSizes = settings?.typography?.fontSizes; + const defaultFontSizesEnabled = !! settings?.typography?.defaultFontSizes; + return [ + ...( fontSizes?.custom ?? [] ), + ...( fontSizes?.theme ?? [] ), + ...( defaultFontSizesEnabled ? fontSizes?.default ?? [] : [] ), + ]; } function TypographyToolsPanel( { @@ -189,7 +193,7 @@ export default function TypographyPanel( { // Font Size const hasFontSizeEnabled = useHasFontSizeControl( settings ); const disableCustomFontSizes = ! settings?.typography?.customFontSize; - const mergedFontSizes = getUniqueFontSizesBySlug( settings ); + const mergedFontSizes = getMergedFontSizes( settings ); const fontSize = decodeValue( inheritedValue?.typography?.fontSize ); const setFontSize = ( newValue, metadata ) => { diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 2148b2bb8e5ce7..d2bbcd26a6eff2 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -213,6 +213,7 @@ export function useBlockSettings( name, parentLayout ) { customFontFamilies, defaultFontFamilies, themeFontFamilies, + defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, @@ -265,6 +266,7 @@ export function useBlockSettings( name, parentLayout ) { 'typography.fontFamilies.custom', 'typography.fontFamilies.default', 'typography.fontFamilies.theme', + 'typography.defaultFontSizes', 'typography.fontSizes.custom', 'typography.fontSizes.default', 'typography.fontSizes.theme', @@ -359,6 +361,7 @@ export function useBlockSettings( name, parentLayout ) { theme: themeFontSizes, }, customFontSize, + defaultFontSizes: defaultFontSizesEnabled, fontStyle, fontWeight, lineHeight, @@ -398,6 +401,7 @@ export function useBlockSettings( name, parentLayout ) { customFontFamilies, defaultFontFamilies, themeFontFamilies, + defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, diff --git a/packages/block-editor/src/utils/object.js b/packages/block-editor/src/utils/object.js index 8f6c82a9c3991e..c78fe0e656dfef 100644 --- a/packages/block-editor/src/utils/object.js +++ b/packages/block-editor/src/utils/object.js @@ -49,3 +49,19 @@ export const getValueFromObjectPath = ( object, path, defaultValue ) => { } ); return value ?? defaultValue; }; + +/** + * Helper util to filter out objects with duplicate values for a given property. + * + * @param {Object[]} array Array of objects to filter. + * @param {string} property Property to filter unique values by. + * + * @return {Object[]} Array of objects with unique values for the specified property. + */ +export function uniqByProperty( array, property ) { + const seen = new Set(); + return array.filter( ( item ) => { + const value = item[ property ]; + return seen.has( value ) ? false : seen.add( value ); + } ); +} diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 568dbc276dd594..563037f41db9dc 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -122,7 +122,7 @@ public function test_get_theme_items() { $data = $response->get_data(); $expected = array( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( diff --git a/phpunit/class-wp-theme-json-schema-test.php b/phpunit/class-wp-theme-json-schema-test.php index 9c1103197d72e5..4259b8b5de6d4f 100644 --- a/phpunit/class-wp-theme-json-schema-test.php +++ b/phpunit/class-wp-theme-json-schema-test.php @@ -35,6 +35,18 @@ public function test_migrate_v1_to_latest() { 'width' => false, ), 'typography' => array( + 'fontSizes' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => 12, + ), + array( + 'name' => 'Normal', + 'slug' => 'normal', + 'size' => 16, + ), + ), 'fontStyle' => false, 'fontWeight' => false, 'letterSpacing' => false, @@ -120,11 +132,24 @@ public function test_migrate_v1_to_latest() { 'width' => false, ), 'typography' => array( - 'fontStyle' => false, - 'fontWeight' => false, - 'letterSpacing' => false, - 'textDecoration' => false, - 'textTransform' => false, + 'defaultFontSizes' => false, + 'fontSizes' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => 12, + ), + array( + 'name' => 'Normal', + 'slug' => 'normal', + 'size' => 16, + ), + ), + 'fontStyle' => false, + 'fontWeight' => false, + 'letterSpacing' => false, + 'textDecoration' => false, + 'textTransform' => false, ), 'blocks' => array( 'core/group' => array( @@ -179,4 +204,51 @@ public function test_migrate_v1_to_latest() { $this->assertEqualSetsWithIndex( $expected, $actual ); } + + public function test_migrate_v2_to_latest() { + $theme_json_v2 = array( + 'version' => 2, + 'settings' => array( + 'typography' => array( + 'fontSizes' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => 12, + ), + array( + 'name' => 'Normal', + 'slug' => 'normal', + 'size' => 16, + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json_v2 ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'typography' => array( + 'defaultFontSizes' => false, + 'fontSizes' => array( + array( + 'name' => 'Small', + 'slug' => 'small', + 'size' => 12, + ), + array( + 'name' => 'Normal', + 'slug' => 'normal', + 'size' => 16, + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 1ab2639b5d539e..65b9f745e4583c 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -3195,7 +3195,7 @@ public function test_get_editor_settings_custom_units_can_be_filtered() { public function test_export_data() { $theme = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3216,7 +3216,7 @@ public function test_export_data() { ); $user = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3240,7 +3240,7 @@ public function test_export_data() { $theme->merge( $user ); $actual = $theme->get_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3270,7 +3270,7 @@ public function test_export_data() { public function test_export_data_deals_with_empty_user_data() { $theme = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3292,7 +3292,7 @@ public function test_export_data_deals_with_empty_user_data() { $actual = $theme->get_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3317,7 +3317,7 @@ public function test_export_data_deals_with_empty_user_data() { public function test_export_data_deals_with_empty_theme_data() { $user = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3340,7 +3340,7 @@ public function test_export_data_deals_with_empty_theme_data() { $actual = $user->get_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( 'palette' => array( @@ -3363,31 +3363,19 @@ public function test_export_data_deals_with_empty_theme_data() { } public function test_export_data_deals_with_empty_data() { - $theme_v2 = new WP_Theme_JSON_Gutenberg( - array( - 'version' => 2, - ), - 'theme' - ); - $actual_v2 = $theme_v2->get_data(); - $expected_v2 = array( 'version' => 2 ); - $this->assertEqualSetsWithIndex( $expected_v2, $actual_v2 ); - - $theme_v1 = new WP_Theme_JSON_Gutenberg( - array( - 'version' => 1, - ), + $theme = new WP_Theme_JSON_Gutenberg( + array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), 'theme' ); - $actual_v1 = $theme_v1->get_data(); - $expected_v1 = array( 'version' => 2 ); - $this->assertEqualSetsWithIndex( $expected_v1, $actual_v1 ); + $actual = $theme->get_data(); + $expected = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } public function test_export_data_sets_appearance_tools() { $theme = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'appearanceTools' => true, 'blocks' => array( @@ -3401,7 +3389,7 @@ public function test_export_data_sets_appearance_tools() { $actual = $theme->get_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'appearanceTools' => true, 'blocks' => array( @@ -3418,7 +3406,7 @@ public function test_export_data_sets_appearance_tools() { public function test_export_data_sets_use_root_padding_aware_alignments() { $theme = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'useRootPaddingAwareAlignments' => true, 'blocks' => array( @@ -3432,7 +3420,7 @@ public function test_export_data_sets_use_root_padding_aware_alignments() { $actual = $theme->get_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'useRootPaddingAwareAlignments' => true, 'blocks' => array( @@ -3513,7 +3501,7 @@ public function test_get_element_class_name_invalid() { public function test_get_property_value_valid() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'color' => array( 'background' => '#ffffff', @@ -3591,7 +3579,7 @@ public function data_get_property_value_should_return_string_for_invalid_paths_o public function test_get_property_value_loop() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'color' => array( 'background' => '#ffffff', @@ -3624,7 +3612,7 @@ public function test_get_property_value_loop() { public function test_get_property_value_recursion() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'color' => array( 'background' => '#ffffff', @@ -3656,7 +3644,7 @@ public function test_get_property_value_recursion() { public function test_get_property_value_self() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'color' => array( 'background' => '#ffffff', @@ -3674,7 +3662,7 @@ public function test_get_property_value_self() { public function test_get_styles_for_block_with_padding_aware_alignments() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'spacing' => array( 'padding' => array( @@ -3705,7 +3693,7 @@ public function test_get_styles_for_block_with_padding_aware_alignments() { public function test_get_styles_for_block_without_padding_aware_alignments() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'spacing' => array( 'padding' => array( @@ -3733,7 +3721,7 @@ public function test_get_styles_for_block_without_padding_aware_alignments() { public function test_get_styles_for_block_with_content_width() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'layout' => array( 'contentSize' => '800px', @@ -3757,7 +3745,7 @@ public function test_get_styles_for_block_with_content_width() { public function test_get_styles_with_appearance_tools() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'appearanceTools' => true, ), @@ -3777,7 +3765,7 @@ public function test_get_styles_with_appearance_tools() { public function test_sanitization() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'spacing' => array( 'blockGap' => 'valid value', @@ -3796,7 +3784,7 @@ public function test_sanitization() { $actual = $theme_json->get_raw_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'spacing' => array( 'blockGap' => 'valid value', @@ -3817,7 +3805,7 @@ public function test_sanitization() { public function test_sanitize_for_unregistered_style_variations() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'blocks' => array( 'core/quote' => array( @@ -3841,7 +3829,7 @@ public function test_sanitize_for_unregistered_style_variations() { $sanitized_theme_json = $theme_json->get_raw_data(); $expected = array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'blocks' => array( 'core/quote' => array( @@ -3868,7 +3856,7 @@ public function test_sanitize_for_unregistered_style_variations() { public function test_sanitize_for_block_with_style_variations( $theme_json_variations, $expected_sanitized ) { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'blocks' => array( 'core/quote' => $theme_json_variations, @@ -3950,7 +3938,7 @@ public function data_sanitize_for_block_with_style_variations() { public function test_sanitize_indexed_arrays() { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => '2', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'badKey2' => 'I am Evil!', 'settings' => array( 'badKey3' => 'I am Evil!', @@ -4018,7 +4006,7 @@ public function test_sanitize_indexed_arrays() { ); $expected_sanitized = array( - 'version' => '2', + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'typography' => array( 'fontFamilies' => array( @@ -4085,7 +4073,7 @@ public function test_sanitize_indexed_arrays() { public function test_sanitize_with_invalid_style_variation( $theme_json_variations ) { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'blocks' => array( 'core/quote' => $theme_json_variations, @@ -4130,7 +4118,7 @@ public function data_sanitize_with_invalid_style_variation() { public function test_get_styles_for_block_with_style_variations( $theme_json_variations, $metadata_variations, $expected ) { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'styles' => array( 'blocks' => array( 'core/quote' => $theme_json_variations, @@ -4282,7 +4270,7 @@ public function test_block_style_variations_with_invalid_properties() { public function test_set_spacing_sizes( $spacing_scale, $expected_output ) { $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'spacing' => array( 'spacingScale' => $spacing_scale, @@ -4572,7 +4560,7 @@ public function test_set_spacing_sizes_when_invalid( $spacing_scale, $expected_o $theme_json = new WP_Theme_JSON_Gutenberg( array( - 'version' => 2, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'spacing' => array( 'spacingScale' => $spacing_scale, diff --git a/phpunit/data/themedir1/block-theme-child-with-fluid-layout/theme.json b/phpunit/data/themedir1/block-theme-child-with-fluid-layout/theme.json index 710ec336df70b2..813024ba8abeb6 100644 --- a/phpunit/data/themedir1/block-theme-child-with-fluid-layout/theme.json +++ b/phpunit/data/themedir1/block-theme-child-with-fluid-layout/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": true, "layout": { diff --git a/phpunit/data/themedir1/block-theme-child-with-fluid-typography-config/theme.json b/phpunit/data/themedir1/block-theme-child-with-fluid-typography-config/theme.json index dcd3745f1630cc..3aa0560aaf0623 100644 --- a/phpunit/data/themedir1/block-theme-child-with-fluid-typography-config/theme.json +++ b/phpunit/data/themedir1/block-theme-child-with-fluid-typography-config/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": true, "layout": { diff --git a/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json b/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json index 7b345242702956..b2624775a50037 100644 --- a/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json +++ b/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": true, "typography": { diff --git a/phpunit/data/themedir1/block-theme-child/styles/variation-a.json b/phpunit/data/themedir1/block-theme-child/styles/variation-a.json index a9d5ade8946928..53c3ef60619b5b 100644 --- a/phpunit/data/themedir1/block-theme-child/styles/variation-a.json +++ b/phpunit/data/themedir1/block-theme-child/styles/variation-a.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "settings": { "blocks": { "core/paragraph": { diff --git a/phpunit/data/themedir1/block-theme-child/styles/variation-b.json b/phpunit/data/themedir1/block-theme-child/styles/variation-b.json index 0a8a4fcab99f61..4e949f24c7f401 100644 --- a/phpunit/data/themedir1/block-theme-child/styles/variation-b.json +++ b/phpunit/data/themedir1/block-theme-child/styles/variation-b.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "settings": { "blocks": { "core/post-title": { diff --git a/phpunit/data/themedir1/block-theme-child/theme.json b/phpunit/data/themedir1/block-theme-child/theme.json index 1157fa91280303..185437e9b81c4b 100644 --- a/phpunit/data/themedir1/block-theme-child/theme.json +++ b/phpunit/data/themedir1/block-theme-child/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "color": { "palette": [ diff --git a/phpunit/data/themedir1/block-theme/styles/variation-a.json b/phpunit/data/themedir1/block-theme/styles/variation-a.json index 42c20fc63b5925..eb08d0090c177c 100644 --- a/phpunit/data/themedir1/block-theme/styles/variation-a.json +++ b/phpunit/data/themedir1/block-theme/styles/variation-a.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "settings": { "blocks": { "core/paragraph": { diff --git a/phpunit/data/themedir1/block-theme/styles/variation-b.json b/phpunit/data/themedir1/block-theme/styles/variation-b.json index 340198ffe0b65f..efdbe8aa86650b 100644 --- a/phpunit/data/themedir1/block-theme/styles/variation-b.json +++ b/phpunit/data/themedir1/block-theme/styles/variation-b.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "settings": { "blocks": { "core/post-title": { diff --git a/phpunit/data/themedir1/block-theme/styles/variation.json b/phpunit/data/themedir1/block-theme/styles/variation.json index d0f316cb454dd9..debb3666a767bd 100644 --- a/phpunit/data/themedir1/block-theme/styles/variation.json +++ b/phpunit/data/themedir1/block-theme/styles/variation.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "title": "Block theme variation", "settings": { "color": { diff --git a/phpunit/data/themedir1/block-theme/theme.json b/phpunit/data/themedir1/block-theme/theme.json index fb24069fb64296..2c29884bf4dcb8 100644 --- a/phpunit/data/themedir1/block-theme/theme.json +++ b/phpunit/data/themedir1/block-theme/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "title": "Block theme", "settings": { "color": { diff --git a/phpunit/data/themedir1/fonts-block-theme/styles/variation-duplicate-fonts.json b/phpunit/data/themedir1/fonts-block-theme/styles/variation-duplicate-fonts.json index 040689043379d6..c24a4a85a4f673 100644 --- a/phpunit/data/themedir1/fonts-block-theme/styles/variation-duplicate-fonts.json +++ b/phpunit/data/themedir1/fonts-block-theme/styles/variation-duplicate-fonts.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "title": "Variation: duplicate fonts", "settings": { "typography": { diff --git a/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-family.json b/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-family.json index 0af954cbecaa80..dfc83f3726d798 100644 --- a/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-family.json +++ b/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-family.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "title": "Variation: new font family", "settings": { "typography": { diff --git a/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-variations.json b/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-variations.json index 81268817ade703..95cb7b88d0fb78 100644 --- a/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-variations.json +++ b/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-variations.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "title": "Variation: new font variations", "settings": { "typography": { diff --git a/phpunit/data/themedir1/fonts-block-theme/styles/variation-no-fonts.json b/phpunit/data/themedir1/fonts-block-theme/styles/variation-no-fonts.json index 9c98c6893fa062..fcdd368ec691b8 100644 --- a/phpunit/data/themedir1/fonts-block-theme/styles/variation-no-fonts.json +++ b/phpunit/data/themedir1/fonts-block-theme/styles/variation-no-fonts.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "title": "Variation - no fonts", "styles": { "typography": { diff --git a/phpunit/data/themedir1/fonts-block-theme/theme.json b/phpunit/data/themedir1/fonts-block-theme/theme.json index a5d40da2b5bb2e..a7946a3f11023c 100644 --- a/phpunit/data/themedir1/fonts-block-theme/theme.json +++ b/phpunit/data/themedir1/fonts-block-theme/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": true, "color": { diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 7cb34945b56810..ccd2ee7f2ce53e 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -499,6 +499,11 @@ "description": "Settings related to typography.", "type": "object", "properties": { + "defaultFontSizes": { + "description": "Allow users to choose font sizes from the default font size presets.", + "type": "boolean", + "default": true + }, "customFontSize": { "description": "Allow users to set custom font sizes.", "type": "boolean", @@ -2244,7 +2249,7 @@ "version": { "description": "Version of theme.json to use.", "type": "integer", - "enum": [ 2 ] + "enum": [ 3 ] }, "title": { "type": "string", diff --git a/test/emptytheme/styles/variation.json b/test/emptytheme/styles/variation.json index 0ab221d087035a..06f672f6fd25d7 100644 --- a/test/emptytheme/styles/variation.json +++ b/test/emptytheme/styles/variation.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "settings": { "color": { "palette": [ diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index d95ed844e6b1cb..62ce296e971c34 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": true, "layout": { diff --git a/test/gutenberg-test-themes/style-variations/styles/pink.json b/test/gutenberg-test-themes/style-variations/styles/pink.json index bdbf2829eb5528..c9e4ac85c1c795 100644 --- a/test/gutenberg-test-themes/style-variations/styles/pink.json +++ b/test/gutenberg-test-themes/style-variations/styles/pink.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "settings": { "color": { "palette": [ diff --git a/test/gutenberg-test-themes/style-variations/styles/yellow.json b/test/gutenberg-test-themes/style-variations/styles/yellow.json index 9bf00e21b42205..df82e52408da36 100644 --- a/test/gutenberg-test-themes/style-variations/styles/yellow.json +++ b/test/gutenberg-test-themes/style-variations/styles/yellow.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "styles": { "color": { "background": "#ffef0b", diff --git a/test/gutenberg-test-themes/style-variations/theme.json b/test/gutenberg-test-themes/style-variations/theme.json index f0fc1ae54042a2..dee779a4ca0df9 100644 --- a/test/gutenberg-test-themes/style-variations/theme.json +++ b/test/gutenberg-test-themes/style-variations/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "styles": { "color": { "background": "red" From a716f0e5606094ca89656c2141996d248918c366 Mon Sep 17 00:00:00 2001 From: Tanner Stokes Date: Thu, 25 Apr 2024 15:42:09 -0400 Subject: [PATCH 10/97] Mobile Release v1.118.0 (#61103) * Release script: Update react-native-editor version to 1.118.0 * Release script: Update CHANGELOG for version 1.118.0 * Release script: Update podfile --- package-lock.json | 6 +++--- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 2 ++ packages/react-native-editor/ios/Podfile.lock | 8 ++++---- packages/react-native-editor/package.json | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 131b3ae8124daf..3979cee9eed1cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54999,7 +54999,7 @@ }, "packages/react-native-aztec": { "name": "@wordpress/react-native-aztec", - "version": "1.117.0", + "version": "1.118.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/element": "file:../element", @@ -55012,7 +55012,7 @@ }, "packages/react-native-bridge": { "name": "@wordpress/react-native-bridge", - "version": "1.117.0", + "version": "1.118.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/react-native-aztec": "file:../react-native-aztec" @@ -55023,7 +55023,7 @@ }, "packages/react-native-editor": { "name": "@wordpress/react-native-editor", - "version": "1.117.0", + "version": "1.118.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index fd10912a798b55..ac47ae64e08141 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.117.0", + "version": "1.118.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index d91e5f7aeceb60..7fd9dff7a0f6bf 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.117.0", + "version": "1.118.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 8381d09b04eb9d..d3e1a0a730584f 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,8 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased + +## 1.118.0 - [*] Fix a crash when pasting file images and special comment markup [#60476] - [*] Update Aztec to v2.1.2 [#61007] - [*] KeyboardAwareFlatList - Enable FlatList virtualization for iOS [#59833] diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index c4c90d94cc1d76..9abe99edd33f13 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.73.3) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.117.0): + - Gutenberg (1.118.0): - React-Core (= 0.73.3) - React-CoreModules (= 0.73.3) - React-RCTImage (= 0.73.3) @@ -1109,7 +1109,7 @@ PODS: - React-Core - RNSVG (14.0.0): - React-Core - - RNTAztecView (1.117.0): + - RNTAztecView (1.118.0): - React-Core - WordPress-Aztec-iOS (= 1.19.11) - SDWebImage (5.11.1): @@ -1343,7 +1343,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 73b3972e2bd20b3235ff2014f06a3d3af675ed29 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 - Gutenberg: 3bc820c1a94b4740a86cdbad9f302d7c3a907ba8 + Gutenberg: c0094e0fdfa895be7ad038dbb7b42dc0d21074cf hermes-engine: 5420539d016f368cd27e008f65f777abd6098c56 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c @@ -1402,7 +1402,7 @@ SPEC CHECKSUMS: RNReanimated: 6936b41d8afb97175e7c0ab40425b53103f71046 RNScreens: 2b73f5eb2ac5d94fbd61fa4be0bfebd345716825 RNSVG: 255767813dac22db1ec2062c8b7e7b856d4e5ae6 - RNTAztecView: e161e7402f1e64e37d4fe1474a73845f0fc1efe9 + RNTAztecView: caf0e76a80867970eaa182cf9bf2d5b3c89285c5 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 28ce3d0c651c93..a682a4ae451866 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.117.0", + "version": "1.118.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From fd50003b33812a41f7ae806ae8ec0126663c84f4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 25 Apr 2024 20:56:15 +0100 Subject: [PATCH 11/97] Editor: Unify the BlockContextualToolbar component between post and site editors (#61104) Co-authored-by: youknowriad Co-authored-by: Mamaduka Co-authored-by: jeryj --- .../style.scss | 5 +- .../edit-post/src/components/header/index.js | 30 +++---- .../src/components/header/style.scss | 79 ------------------- .../src/components/header-edit-mode/index.js | 78 ++++-------------- .../components/header-edit-mode/style.scss | 79 ------------------- .../collapsible-block-toolbar}/index.js | 33 ++++---- .../collapsible-block-toolbar/style.scss | 78 ++++++++++++++++++ packages/editor/src/private-apis.js | 2 + packages/editor/src/style.scss | 1 + 9 files changed, 128 insertions(+), 257 deletions(-) rename packages/{edit-post/src/components/header/contextual-toolbar => editor/src/components/collapsible-block-toolbar}/index.js (60%) create mode 100644 packages/editor/src/components/collapsible-block-toolbar/style.scss diff --git a/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss b/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss index e1ce8bac6064b9..4565473ec95eb4 100644 --- a/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss +++ b/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss @@ -8,9 +8,6 @@ } } -.edit-post-header__toolbar -.selected-block-tools-wrapper -.block-editor-block-toolbar -.block-editor-block-bindings-toolbar-indicator { +.editor-collapsible-block-toolbar .block-editor-block-bindings-toolbar-indicator { height: 32px; } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 6aed47542ebcfe..32f2cc1ccd276d 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { store as blockEditorStore } from '@wordpress/block-editor'; import { DocumentBar, PostSavedState, @@ -18,7 +17,8 @@ import { useSelect } from '@wordpress/data'; import { useViewportMatch } from '@wordpress/compose'; import { __unstableMotion as motion } from '@wordpress/components'; import { store as preferencesStore } from '@wordpress/preferences'; -import { useState, useCallback } from '@wordpress/element'; +import { useState } from '@wordpress/element'; + /** * Internal dependencies */ @@ -26,12 +26,17 @@ import FullscreenModeClose from './fullscreen-mode-close'; import PostEditorMoreMenu from './more-menu'; import PostPublishButtonOrToggle from './post-publish-button-or-toggle'; import MainDashboardButton from './main-dashboard-button'; -import ContextualToolbar from './contextual-toolbar'; import { store as editPostStore } from '../../store'; import { unlock } from '../../lock-unlock'; -const { DocumentTools, PostViewLink, PreviewDropdown, PinnedItems, MoreMenu } = - unlock( editorPrivateApis ); +const { + CollapsableBlockToolbar, + DocumentTools, + PostViewLink, + PreviewDropdown, + PinnedItems, + MoreMenu, +} = unlock( editorPrivateApis ); const slideY = { hidden: { y: '-50px' }, @@ -50,7 +55,6 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) { const isLargeViewport = useViewportMatch( 'medium' ); const { isTextEditor, - blockSelectionStart, hasActiveMetaboxes, isPublishSidebarOpened, showIconLabels, @@ -62,8 +66,6 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) { return { isTextEditor: getEditorMode() === 'text', - blockSelectionStart: - select( blockEditorStore ).getBlockSelectionStart(), hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), hasHistory: !! select( editorStore ).getEditorSettings() @@ -80,13 +82,6 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) { const [ isBlockToolsCollapsed, setIsBlockToolsCollapsed ] = useState( true ); - const handleToggleCollapse = useCallback( - ( isCollapsed ) => { - setIsBlockToolsCollapsed( isCollapsed ); - }, - [ setIsBlockToolsCollapsed ] - ); - return (
@@ -107,10 +102,9 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) { > { hasTopToolbar && ( - ) }
{ const { getEditedPostType } = select( editSiteStore ); - const { getBlockSelectionStart, __unstableGetEditorMode } = - select( blockEditorStore ); + const { __unstableGetEditorMode } = select( blockEditorStore ); const { get: getPreference } = select( preferencesStore ); const { getDeviceType } = select( editorStore ); @@ -66,7 +58,6 @@ export default function HeaderEditMode() { deviceType: getDeviceType(), templateType: getEditedPostType(), blockEditorMode: __unstableGetEditorMode(), - blockSelectionStart: getBlockSelectionStart(), showIconLabels: getPreference( 'core', 'showIconLabels' ), editorCanvasView: unlock( select( editSiteStore ) @@ -77,11 +68,8 @@ export default function HeaderEditMode() { }, [] ); const isLargeViewport = useViewportMatch( 'medium' ); - const hasBlockToolbar = useHasBlockToolbar(); - const hasFixedToolbar = hasBlockToolbar && isFixedToolbar; const showTopToolbar = - isLargeViewport && hasFixedToolbar && blockEditorMode !== 'zoom-out'; - const blockToolbarRef = useRef(); + isLargeViewport && isFixedToolbar && blockEditorMode !== 'zoom-out'; const disableMotion = useReducedMotion(); const hasDefaultEditorCanvasView = ! useHasEditorCanvasContainer(); @@ -93,13 +81,6 @@ export default function HeaderEditMode() { const [ isBlockToolsCollapsed, setIsBlockToolsCollapsed ] = useState( true ); - useEffect( () => { - // If we have a new block selection, show the block tools - if ( blockSelectionStart ) { - setIsBlockToolsCollapsed( false ); - } - }, [ blockSelectionStart ] ); - const toolbarVariants = { isDistractionFree: { y: '-50px' }, isDistractionFreeHovering: { y: 0 }, @@ -130,37 +111,10 @@ export default function HeaderEditMode() { isDistractionFree={ isDistractionFree } /> { showTopToolbar && ( - <> -
- -
- -
+ + ); +} diff --git a/packages/block-editor/src/components/writing-mode-control/style.scss b/packages/block-editor/src/components/segmented-text-control/style.scss similarity index 58% rename from packages/block-editor/src/components/writing-mode-control/style.scss rename to packages/block-editor/src/components/segmented-text-control/style.scss index 4b865dc0282c08..7a4a3bbea7cb33 100644 --- a/packages/block-editor/src/components/writing-mode-control/style.scss +++ b/packages/block-editor/src/components/segmented-text-control/style.scss @@ -1,18 +1,15 @@ -.block-editor-writing-mode-control { +.block-editor-segmented-text-control { border: 0; margin: 0; padding: 0; - .block-editor-writing-mode-control__buttons { + .block-editor-segmented-text-control__buttons { // 4px of padding makes the row 40px high, same as an input. padding: $grid-unit-05 0; display: flex; } .components-button.has-icon { - height: $grid-unit-40; margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; } } diff --git a/packages/block-editor/src/components/text-alignment-control/index.js b/packages/block-editor/src/components/text-alignment-control/index.js new file mode 100644 index 00000000000000..a8e9ee22df7fd6 --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/index.js @@ -0,0 +1,91 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + alignLeft, + alignCenter, + alignRight, + alignJustify, +} from '@wordpress/icons'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + +const TEXT_ALIGNMENT_OPTIONS = [ + { + label: __( 'Align text left' ), + value: 'left', + icon: alignLeft, + }, + { + label: __( 'Align text center' ), + value: 'center', + icon: alignCenter, + }, + { + label: __( 'Align text right' ), + value: 'right', + icon: alignRight, + }, + { + label: __( 'Justify text' ), + value: 'justify', + icon: alignJustify, + }, +]; + +const DEFAULT_OPTIONS = [ 'left', 'center', 'right' ]; + +/** + * Control to facilitate text alignment selections. + * + * @param {Object} props Component props. + * @param {string} props.className Class name to add to the control. + * @param {string} props.value Currently selected text alignment. + * @param {Function} props.onChange Handles change in text alignment selection. + * @param {string[]} props.options Array of text alignment options to display. + * + * @return {Element} Text alignment control. + */ +export default function TextAlignmentControl( { + className, + value, + onChange, + options = DEFAULT_OPTIONS, +} ) { + const validOptions = useMemo( + () => + TEXT_ALIGNMENT_OPTIONS.filter( ( option ) => + options.includes( option.value ) + ), + [ options ] + ); + + if ( ! validOptions.length ) { + return null; + } + + return ( + { + onChange( newValue === value ? undefined : newValue ); + } } + /> + ); +} diff --git a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js new file mode 100644 index 00000000000000..b2c171497acb0a --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TextAlignmentControl from '../'; + +export default { + title: 'BlockEditor/TextAlignmentControl', + component: TextAlignmentControl, + argTypes: { + onChange: { action: 'onChange' }, + className: { control: 'text' }, + options: { + control: 'check', + options: [ 'left', 'center', 'right', 'justify' ], + }, + value: { control: { type: null } }, + }, +}; + +const Template = ( { onChange, ...args } ) => { + const [ value, setValue ] = useState(); + return ( + { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/text-decoration-control/index.js b/packages/block-editor/src/components/text-decoration-control/index.js index 973fb71a7448f1..6a2085e980bd4e 100644 --- a/packages/block-editor/src/components/text-decoration-control/index.js +++ b/packages/block-editor/src/components/text-decoration-control/index.js @@ -6,23 +6,27 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { reset, formatStrikethrough, formatUnderline } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const TEXT_DECORATIONS = [ { - name: __( 'None' ), + label: __( 'None' ), value: 'none', icon: reset, }, { - name: __( 'Underline' ), + label: __( 'Underline' ), value: 'underline', icon: formatUnderline, }, { - name: __( 'Strikethrough' ), + label: __( 'Strikethrough' ), value: 'line-through', icon: formatStrikethrough, }, @@ -31,10 +35,10 @@ const TEXT_DECORATIONS = [ /** * Control to facilitate text decoration selections. * - * @param {Object} props Component props. - * @param {string} props.value Currently selected text decoration. - * @param {Function} props.onChange Handles change in text decoration selection. - * @param {string} [props.className] Additional class name to apply. + * @param {Object} props Component props. + * @param {string} props.value Currently selected text decoration. + * @param {Function} props.onChange Handles change in text decoration selection. + * @param {string} props.className Additional class name to apply. * * @return {Element} Text decoration control. */ @@ -44,34 +48,17 @@ export default function TextDecorationControl( { className, } ) { return ( -
- - { __( 'Decoration' ) } - -
- { TEXT_DECORATIONS.map( ( textDecoration ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/components/text-decoration-control/style.scss b/packages/block-editor/src/components/text-decoration-control/style.scss deleted file mode 100644 index 689707a66b7ca9..00000000000000 --- a/packages/block-editor/src/components/text-decoration-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-decoration-control { - border: 0; - margin: 0; - padding: 0; - - .block-editor-text-decoration-control__buttons { - // 4px of padding makes the row 40px high, same as an input. - padding: $grid-unit-05 0; - display: flex; - } - - .components-button.has-icon { - height: $grid-unit-40; - margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; - } -} diff --git a/packages/block-editor/src/components/text-transform-control/index.js b/packages/block-editor/src/components/text-transform-control/index.js index c5d0ce37e9acd1..8fd1fb9cfdf51b 100644 --- a/packages/block-editor/src/components/text-transform-control/index.js +++ b/packages/block-editor/src/components/text-transform-control/index.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { reset, @@ -15,24 +14,29 @@ import { formatUppercase, } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const TEXT_TRANSFORMS = [ { - name: __( 'None' ), + label: __( 'None' ), value: 'none', icon: reset, }, { - name: __( 'Uppercase' ), + label: __( 'Uppercase' ), value: 'uppercase', icon: formatUppercase, }, { - name: __( 'Lowercase' ), + label: __( 'Lowercase' ), value: 'lowercase', icon: formatLowercase, }, { - name: __( 'Capitalize' ), + label: __( 'Capitalize' ), value: 'capitalize', icon: formatCapitalize, }, @@ -50,34 +54,17 @@ const TEXT_TRANSFORMS = [ */ export default function TextTransformControl( { className, value, onChange } ) { return ( -
- - { __( 'Letter case' ) } - -
- { TEXT_TRANSFORMS.map( ( textTransform ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/components/text-transform-control/style.scss b/packages/block-editor/src/components/text-transform-control/style.scss deleted file mode 100644 index 0e097405e332b7..00000000000000 --- a/packages/block-editor/src/components/text-transform-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-transform-control { - border: 0; - margin: 0; - padding: 0; - - .block-editor-text-transform-control__buttons { - // 4px of padding makes the row 40px high, same as an input. - padding: $grid-unit-05 0; - display: flex; - } - - .components-button.has-icon { - height: $grid-unit-40; - margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; - } -} diff --git a/packages/block-editor/src/components/writing-mode-control/index.js b/packages/block-editor/src/components/writing-mode-control/index.js index 2adf8be14ad395..420c3d75ba31ae 100644 --- a/packages/block-editor/src/components/writing-mode-control/index.js +++ b/packages/block-editor/src/components/writing-mode-control/index.js @@ -6,18 +6,22 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { __, isRTL } from '@wordpress/i18n'; import { textHorizontal, textVertical } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const WRITING_MODES = [ { - name: __( 'Horizontal' ), + label: __( 'Horizontal' ), value: 'horizontal-tb', icon: textHorizontal, }, { - name: __( 'Vertical' ), + label: __( 'Vertical' ), value: isRTL() ? 'vertical-lr' : 'vertical-rl', icon: textVertical, }, @@ -35,34 +39,17 @@ const WRITING_MODES = [ */ export default function WritingModeControl( { className, value, onChange } ) { return ( -
- - { __( 'Orientation' ) } - -
- { WRITING_MODES.map( ( writingMode ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index e81a5eed39d5bc..f10fcc4df2c726 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -23,6 +23,7 @@ import { BlockRemovalWarningModal } from './components/block-removal-warning-mod import { useLayoutClasses, useLayoutStyles } from './hooks'; import DimensionsTool from './components/dimensions-tool'; import ResolutionTool from './components/resolution-tool'; +import TextAlignmentControl from './components/text-alignment-control'; import { default as ReusableBlocksRenameHint, useReusableBlocksRenameHint, @@ -66,6 +67,7 @@ lock( privateApis, { useLayoutStyles, DimensionsTool, ResolutionTool, + TextAlignmentControl, ReusableBlocksRenameHint, useReusableBlocksRenameHint, usesContextKey, diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 535d1005d274de..5080aa05718bb3 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -41,9 +41,8 @@ @import "./components/multi-selection-inspector/style.scss"; @import "./components/responsive-block-control/style.scss"; @import "./components/rich-text/style.scss"; +@import "./components/segmented-text-control/style.scss"; @import "./components/skip-to-selected-block/style.scss"; -@import "./components/text-decoration-control/style.scss"; -@import "./components/text-transform-control/style.scss"; @import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss"; From 9725060a5b18904c6cc5fdbe4b06fbde7419e02c Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Fri, 26 Apr 2024 10:46:23 +0200 Subject: [PATCH 20/97] useBlockRefs: use more efficient lookup map, use uSES (#60945) * useBlockRefs: use more efficient lookup map, use uSES * Rewrite block refs with observableMap, which moves to compose * Improve docs * Add changelog entry --- .../use-block-props/use-block-refs.js | 74 +++++-------------- .../provider/block-refs-provider.js | 11 +-- .../bubbles-virtually/slot-fill-context.ts | 3 +- .../bubbles-virtually/slot-fill-provider.tsx | 2 +- .../bubbles-virtually/use-slot-fills.ts | 2 +- .../slot-fill/bubbles-virtually/use-slot.ts | 2 +- packages/components/src/slot-fill/types.ts | 4 +- packages/compose/CHANGELOG.md | 2 + packages/compose/README.md | 21 ++++++ .../src/hooks/use-observable-value/index.ts | 35 +++++++++ .../hooks/use-observable-value/test/index.js | 42 +++++++++++ packages/compose/src/index.js | 3 + packages/compose/src/index.native.js | 3 + .../src/utils/observable-map/index.ts} | 36 +++------ .../src/utils/observable-map/test/index.js} | 42 +---------- 15 files changed, 143 insertions(+), 139 deletions(-) create mode 100644 packages/compose/src/hooks/use-observable-value/index.ts create mode 100644 packages/compose/src/hooks/use-observable-value/test/index.js rename packages/{components/src/slot-fill/bubbles-virtually/observable-map.ts => compose/src/utils/observable-map/index.ts} (58%) rename packages/{components/src/slot-fill/test/observable-map.js => compose/src/utils/observable-map/test/index.js} (55%) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js index 56e424319739e1..297aed456df7fc 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-refs.js @@ -1,14 +1,8 @@ /** * WordPress dependencies */ -import { - useContext, - useLayoutEffect, - useMemo, - useRef, - useState, -} from '@wordpress/element'; -import { useRefEffect } from '@wordpress/compose'; +import { useContext, useMemo, useRef } from '@wordpress/element'; +import { useRefEffect, useObservableValue } from '@wordpress/compose'; /** * Internal dependencies @@ -26,60 +20,40 @@ import { BlockRefs } from '../../provider/block-refs-provider'; * @return {RefCallback} Ref callback. */ export function useBlockRefProvider( clientId ) { - const { refs, callbacks } = useContext( BlockRefs ); - const ref = useRef(); - useLayoutEffect( () => { - refs.set( ref, clientId ); - return () => { - refs.delete( ref ); - }; - }, [ clientId ] ); + const { refsMap } = useContext( BlockRefs ); return useRefEffect( ( element ) => { - // Update the ref in the provider. - ref.current = element; - // Call any update functions. - callbacks.forEach( ( id, setElement ) => { - if ( clientId === id ) { - setElement( element ); - } - } ); + refsMap.set( clientId, element ); + return () => refsMap.delete( clientId ); }, [ clientId ] ); } /** - * Gets a ref pointing to the current block element. Continues to return a - * stable ref even if the block client ID changes. + * Gets a ref pointing to the current block element. Continues to return the same + * stable ref object even if the `clientId` argument changes. This hook is not + * reactive, i.e., it won't trigger a rerender of the calling component if the + * ref value changes. For reactive use cases there is the `useBlockElement` hook. * * @param {string} clientId The client ID to get a ref for. * * @return {RefObject} A ref containing the element. */ function useBlockRef( clientId ) { - const { refs } = useContext( BlockRefs ); - const freshClientId = useRef(); - freshClientId.current = clientId; + const { refsMap } = useContext( BlockRefs ); + const latestClientId = useRef(); + latestClientId.current = clientId; + // Always return an object, even if no ref exists for a given client ID, so // that `current` works at a later point. return useMemo( () => ( { get current() { - let element = null; - - // Multiple refs may be created for a single block. Find the - // first that has an element set. - for ( const [ ref, id ] of refs.entries() ) { - if ( id === freshClientId.current && ref.current ) { - element = ref.current; - } - } - - return element; + return refsMap.get( latestClientId.current ) ?? null; }, } ), - [] + [ refsMap ] ); } @@ -92,22 +66,8 @@ function useBlockRef( clientId ) { * @return {Element|null} The block's wrapper element. */ function useBlockElement( clientId ) { - const { callbacks } = useContext( BlockRefs ); - const ref = useBlockRef( clientId ); - const [ element, setElement ] = useState( null ); - - useLayoutEffect( () => { - if ( ! clientId ) { - return; - } - - callbacks.set( setElement, clientId ); - return () => { - callbacks.delete( setElement ); - }; - }, [ clientId ] ); - - return ref.current || element; + const { refsMap } = useContext( BlockRefs ); + return useObservableValue( refsMap, clientId ) ?? null; } export { useBlockRef as __unstableUseBlockRef }; diff --git a/packages/block-editor/src/components/provider/block-refs-provider.js b/packages/block-editor/src/components/provider/block-refs-provider.js index 3f2d19b658a630..e54680356efda2 100644 --- a/packages/block-editor/src/components/provider/block-refs-provider.js +++ b/packages/block-editor/src/components/provider/block-refs-provider.js @@ -2,17 +2,12 @@ * WordPress dependencies */ import { createContext, useMemo } from '@wordpress/element'; +import { observableMap } from '@wordpress/compose'; -export const BlockRefs = createContext( { - refs: new Map(), - callbacks: new Map(), -} ); +export const BlockRefs = createContext( { refsMap: observableMap() } ); export function BlockRefsProvider( { children } ) { - const value = useMemo( - () => ( { refs: new Map(), callbacks: new Map() } ), - [] - ); + const value = useMemo( () => ( { refsMap: observableMap() } ), [] ); return ( { children } ); diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts index e5af99fd3c95a7..a144a7dc33f464 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts +++ b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts @@ -3,11 +3,12 @@ */ import { createContext } from '@wordpress/element'; import warning from '@wordpress/warning'; +import { observableMap } from '@wordpress/compose'; + /** * Internal dependencies */ import type { SlotFillBubblesVirtuallyContext } from '../types'; -import { observableMap } from './observable-map'; const initialContextValue: SlotFillBubblesVirtuallyContext = { slots: observableMap(), diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx index b68e06d05e60ad..bce3175e658c39 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx @@ -3,6 +3,7 @@ */ import { useMemo } from '@wordpress/element'; import isShallowEqual from '@wordpress/is-shallow-equal'; +import { observableMap } from '@wordpress/compose'; /** * Internal dependencies @@ -12,7 +13,6 @@ import type { SlotFillProviderProps, SlotFillBubblesVirtuallyContext, } from '../types'; -import { observableMap } from './observable-map'; function createSlotRegistry(): SlotFillBubblesVirtuallyContext { const slots: SlotFillBubblesVirtuallyContext[ 'slots' ] = observableMap(); diff --git a/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts b/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts index 819c43c4e78917..6229d20f2da513 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts +++ b/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts @@ -2,13 +2,13 @@ * WordPress dependencies */ import { useContext } from '@wordpress/element'; +import { useObservableValue } from '@wordpress/compose'; /** * Internal dependencies */ import SlotFillContext from './slot-fill-context'; import type { SlotKey } from '../types'; -import { useObservableValue } from './observable-map'; export default function useSlotFills( name: SlotKey ) { const registry = useContext( SlotFillContext ); diff --git a/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts b/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts index 6d211fbb3fa37f..d1d37e1d8e541c 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts +++ b/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts @@ -2,6 +2,7 @@ * WordPress dependencies */ import { useMemo, useContext } from '@wordpress/element'; +import { useObservableValue } from '@wordpress/compose'; /** * Internal dependencies @@ -13,7 +14,6 @@ import type { FillProps, SlotKey, } from '../types'; -import { useObservableValue } from './observable-map'; export default function useSlot( name: SlotKey ) { const registry = useContext( SlotFillContext ); diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts index 5e24ba20c72b4e..1711e04cbb1f47 100644 --- a/packages/components/src/slot-fill/types.ts +++ b/packages/components/src/slot-fill/types.ts @@ -4,9 +4,9 @@ import type { Component, MutableRefObject, ReactNode, RefObject } from 'react'; /** - * Internal dependencies + * WordPress dependencies */ -import type { ObservableMap } from './bubbles-virtually/observable-map'; +import type { ObservableMap } from '@wordpress/compose'; export type DistributiveOmit< T, K extends keyof any > = T extends any ? Omit< T, K > diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 90e585bb1b6a05..8560cf4a3222f8 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Added new `observableMap` data structure and `useObservableValue` React hook ([#60945](https://github.com/WordPress/gutenberg/pull/60945)). + ## 6.33.0 (2024-04-19) ## 6.32.0 (2024-04-03) diff --git a/packages/compose/README.md b/packages/compose/README.md index e2c84e17bb849d..83bde196033a01 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -129,6 +129,14 @@ _Returns_ - Higher-order component. +### observableMap + +A constructor (factory) for `ObservableMap`, a map-like key/value data structure where the individual entries are observable: using the `subscribe` method, you can subscribe to updates for a particular keys. Each subscriber always observes one specific key and is not notified about any unrelated changes (for different keys) in the `ObservableMap`. + +_Returns_ + +- `ObservableMap< K, V >`: A new instance of the `ObservableMap` type. + ### pipe Composes multiple higher-order components into a single higher-order component. Performs left-to-right function composition, where each successive invocation is supplied the return value of the previous. @@ -442,6 +450,19 @@ _Returns_ - `import('react').RefCallback>`: The merged ref callback. +### useObservableValue + +React hook that lets you observe an entry in an `ObservableMap`. The hook returns the current value corresponding to the key, or `undefined` when there is no value stored. It also observes changes to the value and triggers an update of the calling component in case the value changes. + +_Parameters_ + +- _map_ `ObservableMap< K, V >`: The `ObservableMap` to observe. +- _name_ `K`: The map key to observe. + +_Returns_ + +- `V | undefined`: The value corresponding to the map key requested. + ### usePrevious Use something's value from the previous render. Based on . diff --git a/packages/compose/src/hooks/use-observable-value/index.ts b/packages/compose/src/hooks/use-observable-value/index.ts new file mode 100644 index 00000000000000..b07bf41f9b20b6 --- /dev/null +++ b/packages/compose/src/hooks/use-observable-value/index.ts @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { useMemo, useSyncExternalStore } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { ObservableMap } from '../../utils/observable-map'; + +/** + * React hook that lets you observe an entry in an `ObservableMap`. The hook returns the + * current value corresponding to the key, or `undefined` when there is no value stored. + * It also observes changes to the value and triggers an update of the calling component + * in case the value changes. + * + * @template K The type of the keys in the map. + * @template V The type of the values in the map. + * @param map The `ObservableMap` to observe. + * @param name The map key to observe. + * @return The value corresponding to the map key requested. + */ +export default function useObservableValue< K, V >( + map: ObservableMap< K, V >, + name: K +): V | undefined { + const [ subscribe, getValue ] = useMemo( + () => [ + ( listener: () => void ) => map.subscribe( name, listener ), + () => map.get( name ), + ], + [ map, name ] + ); + return useSyncExternalStore( subscribe, getValue, getValue ); +} diff --git a/packages/compose/src/hooks/use-observable-value/test/index.js b/packages/compose/src/hooks/use-observable-value/test/index.js new file mode 100644 index 00000000000000..b53adf22f76b1e --- /dev/null +++ b/packages/compose/src/hooks/use-observable-value/test/index.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { render, screen, act } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import { observableMap } from '../../../utils/observable-map'; +import useObservableValue from '..'; + +describe( 'useObservableValue', () => { + test( 'reacts only to the specified key', () => { + const map = observableMap(); + map.set( 'a', 1 ); + + const MapUI = jest.fn( () => { + const value = useObservableValue( map, 'a' ); + return
value is { value }
; + } ); + + render( ); + expect( screen.getByText( /^value is/ ) ).toHaveTextContent( + 'value is 1' + ); + expect( MapUI ).toHaveBeenCalledTimes( 1 ); + + act( () => { + map.set( 'a', 2 ); + } ); + expect( screen.getByText( /^value is/ ) ).toHaveTextContent( + 'value is 2' + ); + expect( MapUI ).toHaveBeenCalledTimes( 2 ); + + // check that setting unobserved map key doesn't trigger a render at all + act( () => { + map.set( 'b', 1 ); + } ); + expect( MapUI ).toHaveBeenCalledTimes( 2 ); + } ); +} ); diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 3d03463f490794..f7e1d1618f97fb 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -4,6 +4,8 @@ export * from './utils/create-higher-order-component'; export * from './utils/debounce'; // The `throttle` helper and its types. export * from './utils/throttle'; +// The `ObservableMap` data structure +export * from './utils/observable-map'; // The `compose` and `pipe` helpers (inspired by `flowRight` and `flow` from Lodash). export { default as compose } from './higher-order/compose'; @@ -46,3 +48,4 @@ export { default as useRefEffect } from './hooks/use-ref-effect'; export { default as __experimentalUseDropZone } from './hooks/use-drop-zone'; export { default as useFocusableIframe } from './hooks/use-focusable-iframe'; export { default as __experimentalUseFixedWindowList } from './hooks/use-fixed-window-list'; +export { default as useObservableValue } from './hooks/use-observable-value'; diff --git a/packages/compose/src/index.native.js b/packages/compose/src/index.native.js index 8d0953b81a14e5..4f3bf5f7603816 100644 --- a/packages/compose/src/index.native.js +++ b/packages/compose/src/index.native.js @@ -4,6 +4,8 @@ export * from './utils/create-higher-order-component'; export * from './utils/debounce'; // The `throttle` helper and its types. export * from './utils/throttle'; +// The `ObservableMap` data structure +export * from './utils/observable-map'; // The `compose` and `pipe` helpers (inspired by `flowRight` and `flow` from Lodash). export { default as compose } from './higher-order/compose'; @@ -39,3 +41,4 @@ export { default as useThrottle } from './hooks/use-throttle'; export { default as useMergeRefs } from './hooks/use-merge-refs'; export { default as useRefEffect } from './hooks/use-ref-effect'; export { default as useNetworkConnectivity } from './hooks/use-network-connectivity'; +export { default as useObservableValue } from './hooks/use-observable-value'; diff --git a/packages/components/src/slot-fill/bubbles-virtually/observable-map.ts b/packages/compose/src/utils/observable-map/index.ts similarity index 58% rename from packages/components/src/slot-fill/bubbles-virtually/observable-map.ts rename to packages/compose/src/utils/observable-map/index.ts index f4c27077e3f458..3442c1a3f94c83 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/observable-map.ts +++ b/packages/compose/src/utils/observable-map/index.ts @@ -1,8 +1,3 @@ -/** - * WordPress dependencies - */ -import { useMemo, useSyncExternalStore } from '@wordpress/element'; - export type ObservableMap< K, V > = { get( name: K ): V | undefined; set( name: K, value: V ): void; @@ -11,8 +6,15 @@ export type ObservableMap< K, V > = { }; /** - * A key/value map where the individual entries are observable by subscribing to them - * with the `subscribe` methods. + * A constructor (factory) for `ObservableMap`, a map-like key/value data structure + * where the individual entries are observable: using the `subscribe` method, you can + * subscribe to updates for a particular keys. Each subscriber always observes one + * specific key and is not notified about any unrelated changes (for different keys) + * in the `ObservableMap`. + * + * @template K The type of the keys in the map. + * @template V The type of the values in the map. + * @return A new instance of the `ObservableMap` type. */ export function observableMap< K, V >(): ObservableMap< K, V > { const map = new Map< K, V >(); @@ -57,23 +59,3 @@ export function observableMap< K, V >(): ObservableMap< K, V > { }, }; } - -/** - * React hook that lets you observe an individual entry in an `ObservableMap`. - * - * @param map The `ObservableMap` to observe. - * @param name The map key to observe. - */ -export function useObservableValue< K, V >( - map: ObservableMap< K, V >, - name: K -): V | undefined { - const [ subscribe, getValue ] = useMemo( - () => [ - ( listener: () => void ) => map.subscribe( name, listener ), - () => map.get( name ), - ], - [ map, name ] - ); - return useSyncExternalStore( subscribe, getValue, getValue ); -} diff --git a/packages/components/src/slot-fill/test/observable-map.js b/packages/compose/src/utils/observable-map/test/index.js similarity index 55% rename from packages/components/src/slot-fill/test/observable-map.js rename to packages/compose/src/utils/observable-map/test/index.js index ee3b3533bdd3ca..5383189c106306 100644 --- a/packages/components/src/slot-fill/test/observable-map.js +++ b/packages/compose/src/utils/observable-map/test/index.js @@ -1,15 +1,7 @@ -/** - * External dependencies - */ -import { render, screen, act } from '@testing-library/react'; - /** * Internal dependencies */ -import { - observableMap, - useObservableValue, -} from '../bubbles-virtually/observable-map'; +import { observableMap } from '..'; describe( 'ObservableMap', () => { test( 'should observe individual values', () => { @@ -49,35 +41,3 @@ describe( 'ObservableMap', () => { expect( listenerB ).toHaveBeenCalledTimes( 1 ); } ); } ); - -describe( 'useObservableValue', () => { - test( 'reacts only to the specified key', () => { - const map = observableMap(); - map.set( 'a', 1 ); - - const MapUI = jest.fn( () => { - const value = useObservableValue( map, 'a' ); - return
value is { value }
; - } ); - - render( ); - expect( screen.getByText( /^value is/ ) ).toHaveTextContent( - 'value is 1' - ); - expect( MapUI ).toHaveBeenCalledTimes( 1 ); - - act( () => { - map.set( 'a', 2 ); - } ); - expect( screen.getByText( /^value is/ ) ).toHaveTextContent( - 'value is 2' - ); - expect( MapUI ).toHaveBeenCalledTimes( 2 ); - - // check that setting unobserved map key doesn't trigger a render at all - act( () => { - map.set( 'b', 1 ); - } ); - expect( MapUI ).toHaveBeenCalledTimes( 2 ); - } ); -} ); From 2b1b80ea98de13ccd28b537cfaadee0cd601647c Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:01:58 +0200 Subject: [PATCH 21/97] useBlockProps: remove dead code (#61133) Co-authored-by: ellatrix Co-authored-by: youknowriad --- .../use-block-moving-mode-class-names.js | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-block-moving-mode-class-names.js diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-moving-mode-class-names.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-moving-mode-class-names.js deleted file mode 100644 index 686e719196f982..00000000000000 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-moving-mode-class-names.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../../store'; - -/** - * Returns the class names used for block moving mode. - * - * @param {string} clientId The block client ID to insert above. - * - * @return {string} The class names. - */ -export function useBlockMovingModeClassNames( clientId ) { - return useSelect( - ( select ) => { - const { - hasBlockMovingClientId, - canInsertBlockType, - getBlockName, - getBlockRootClientId, - isBlockSelected, - } = select( blockEditorStore ); - - // The classes are only relevant for the selected block. Avoid - // re-rendering all blocks! - if ( ! isBlockSelected( clientId ) ) { - return; - } - - const movingClientId = hasBlockMovingClientId(); - - if ( ! movingClientId ) { - return; - } - - return classnames( 'is-block-moving-mode', { - 'can-insert-moving-block': canInsertBlockType( - getBlockName( movingClientId ), - getBlockRootClientId( clientId ) - ), - } ); - }, - [ clientId ] - ); -} From 0c4eab66eefbe703c094824b964826edec0143e0 Mon Sep 17 00:00:00 2001 From: Damon Cook Date: Fri, 26 Apr 2024 05:48:20 -0400 Subject: [PATCH 22/97] Documentation: Add AutosaveMonitor component JSDoc enhancements (#60905) Co-authored-by: colorful-tones Co-authored-by: ntsekouras --- packages/editor/README.md | 18 ++++++++++++- .../src/components/autosave-monitor/index.js | 27 ++++++++++--------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index 39edf9029aca2a..adbb3f0e027973 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -128,7 +128,14 @@ Example: ### AutosaveMonitor -AutosaveMonitor component. Monitors the changes made to the edited post and triggers autosave if necessary. +Monitors the changes made to the edited post and triggers autosave if necessary. + +The logic is straightforward: a check is performed every `props.interval` seconds. If any changes are detected, `props.autosave()` is called. The time between the change and the autosave varies but is no larger than `props.interval` seconds. Refer to the code below for more details, such as the specific way of detecting changes. + +There are two caveats: + +- If `props.isAutosaveable` happens to be false at a time of checking for changes, the check is retried every second. +- The timer may be disabled by setting `props.disableIntervalChecks` to `true`. In that mode, any change will immediately trigger `props.autosave()`. _Usage_ @@ -136,6 +143,15 @@ _Usage_ ``` +_Parameters_ + +- _props_ `Object`: - The properties passed to the component. +- _props.autosave_ `Function`: - The function to call when changes need to be saved. +- _props.interval_ `number`: - The maximum time in seconds between an unsaved change and an autosave. +- _props.isAutosaveable_ `boolean`: - If false, the check for changes is retried every second. +- _props.disableIntervalChecks_ `boolean`: - If true, disables the timer and any change will immediately trigger `props.autosave()`. +- _props.isDirty_ `boolean`: - Indicates if there are unsaved changes. + ### BlockAlignmentToolbar > **Deprecated** since 5.3, use `wp.blockEditor.BlockAlignmentToolbar` instead. diff --git a/packages/editor/src/components/autosave-monitor/index.js b/packages/editor/src/components/autosave-monitor/index.js index 10cf5196fb77b3..2800b4bf0fc1f1 100644 --- a/packages/editor/src/components/autosave-monitor/index.js +++ b/packages/editor/src/components/autosave-monitor/index.js @@ -11,17 +11,6 @@ import { store as coreStore } from '@wordpress/core-data'; */ import { store as editorStore } from '../../store'; -/** - * AutosaveMonitor invokes `props.autosave()` within at most `interval` seconds after an unsaved change is detected. - * - * The logic is straightforward: a check is performed every `props.interval` seconds. If any changes are detected, `props.autosave()` is called. - * The time between the change and the autosave varies but is no larger than `props.interval` seconds. Refer to the code below for more details, such as - * the specific way of detecting changes. - * - * There are two caveats: - * * If `props.isAutosaveable` happens to be false at a time of checking for changes, the check is retried every second. - * * The timer may be disabled by setting `props.disableIntervalChecks` to `true`. In that mode, any change will immediately trigger `props.autosave()`. - */ export class AutosaveMonitor extends Component { constructor( props ) { super( props ); @@ -92,9 +81,23 @@ export class AutosaveMonitor extends Component { } /** - * AutosaveMonitor component. * Monitors the changes made to the edited post and triggers autosave if necessary. * + * The logic is straightforward: a check is performed every `props.interval` seconds. If any changes are detected, `props.autosave()` is called. + * The time between the change and the autosave varies but is no larger than `props.interval` seconds. Refer to the code below for more details, such as + * the specific way of detecting changes. + * + * There are two caveats: + * * If `props.isAutosaveable` happens to be false at a time of checking for changes, the check is retried every second. + * * The timer may be disabled by setting `props.disableIntervalChecks` to `true`. In that mode, any change will immediately trigger `props.autosave()`. + * + * @param {Object} props - The properties passed to the component. + * @param {Function} props.autosave - The function to call when changes need to be saved. + * @param {number} props.interval - The maximum time in seconds between an unsaved change and an autosave. + * @param {boolean} props.isAutosaveable - If false, the check for changes is retried every second. + * @param {boolean} props.disableIntervalChecks - If true, disables the timer and any change will immediately trigger `props.autosave()`. + * @param {boolean} props.isDirty - Indicates if there are unsaved changes. + * * @example * ```jsx * From e8878c19a389f12df941cba9eb44326dafa856f0 Mon Sep 17 00:00:00 2001 From: Damon Cook Date: Fri, 26 Apr 2024 05:53:50 -0400 Subject: [PATCH 23/97] Documentation: Add EditorSnackbars component docs (#61110) * Add doc block for EditorSnackbars * Generate EditorSnackbars doc npm run docs:build --- packages/editor/README.md | 6 +++++- packages/editor/src/components/editor-snackbars/index.js | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index adbb3f0e027973..17b9f97e3b6920 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -330,7 +330,11 @@ Undocumented declaration. ### EditorSnackbars -Undocumented declaration. +Renders the editor snackbars component. + +_Returns_ + +- `JSX.Element`: The rendered component. ### EntitiesSavedStates diff --git a/packages/editor/src/components/editor-snackbars/index.js b/packages/editor/src/components/editor-snackbars/index.js index 8827f97a97c7ce..6530e1ec7ea902 100644 --- a/packages/editor/src/components/editor-snackbars/index.js +++ b/packages/editor/src/components/editor-snackbars/index.js @@ -8,6 +8,11 @@ import { store as noticesStore } from '@wordpress/notices'; // Last three notices. Slices from the tail end of the list. const MAX_VISIBLE_NOTICES = -3; +/** + * Renders the editor snackbars component. + * + * @return {JSX.Element} The rendered component. + */ export default function EditorSnackbars() { const notices = useSelect( ( select ) => select( noticesStore ).getNotices(), From 95f27142a9e6625262fd4c89b1a89bd23c0a4e3e Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:30:57 +0200 Subject: [PATCH 24/97] Block: remove outline related store selecting (#61139) Co-authored-by: ellatrix Co-authored-by: Mamaduka --- .../src/components/block-list/block.js | 15 ++--------- .../src/components/block-list/content.scss | 2 +- .../src/components/block-list/index.js | 3 ++- .../block-list/use-block-props/index.js | 5 +--- .../use-block-props/use-is-hovered.js | 27 ++++++++----------- 5 files changed, 17 insertions(+), 35 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 290d2f05414828..1a1b77f7cd5149 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -534,7 +534,6 @@ function BlockListBlockProvider( props ) { hasSelectedInnerBlock, getBlockIndex, - isTyping, isBlockMultiSelected, isBlockSubtreeDisabled, isBlockHighlighted, @@ -566,11 +565,8 @@ function BlockListBlockProvider( props ) { const attributes = getBlockAttributes( clientId ); const { name: blockName, isValid } = blockWithoutAttributes; const blockType = getBlockType( blockName ); - const { - outlineMode, - supportsLayout, - __unstableIsPreviewMode: isPreviewMode, - } = getSettings(); + const { supportsLayout, __unstableIsPreviewMode: isPreviewMode } = + getSettings(); const hasLightBlockWrapper = blockType?.apiVersion > 1; const previewContext = { isPreviewMode, @@ -606,7 +602,6 @@ function BlockListBlockProvider( props ) { clientId, checkDeep ); - const typing = isTyping(); const movingClientId = hasBlockMovingClientId(); const blockEditingMode = getBlockEditingMode( clientId ); @@ -639,7 +634,6 @@ function BlockListBlockProvider( props ) { isSubtreeDisabled: blockEditingMode === 'disabled' && isBlockSubtreeDisabled( clientId ), - isOutlineEnabled: outlineMode, hasOverlay: __unstableHasActiveBlockOverlayActive( clientId ) && ! isDragging(), @@ -657,7 +651,6 @@ function BlockListBlockProvider( props ) { ! __unstableSelectionHasUnmergeableBlock(), isDragging: isBlockBeingDragged( clientId ), hasChildSelected: isAncestorOfSelectedBlock, - removeOutline: _isSelected && outlineMode && typing, isBlockMovingMode: !! movingClientId, canInsertMovingBlock: movingClientId && @@ -697,7 +690,6 @@ function BlockListBlockProvider( props ) { blockApiVersion, blockTitle, isSubtreeDisabled, - isOutlineEnabled, hasOverlay, initialPosition, isHighlighted, @@ -706,7 +698,6 @@ function BlockListBlockProvider( props ) { isReusable, isDragging, hasChildSelected, - removeOutline, isBlockMovingMode, canInsertMovingBlock, templateLock, @@ -743,7 +734,6 @@ function BlockListBlockProvider( props ) { blockTitle, isSelected, isSubtreeDisabled, - isOutlineEnabled, hasOverlay, initialPosition, blockEditingMode, @@ -753,7 +743,6 @@ function BlockListBlockProvider( props ) { isReusable, isDragging, hasChildSelected, - removeOutline, isBlockMovingMode, canInsertMovingBlock, templateLock, diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index 3971df611c277e..6e65b38310b671 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -270,7 +270,7 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b } } -.is-outline-mode .block-editor-block-list__block:not(.remove-outline) { +.is-outline-mode .block-editor-block-list__block { &.is-hovered { cursor: default; diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 1faffef6ba3941..ea9208b562c3a5 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -56,10 +56,11 @@ function Root( { className, ...settings } ) { getSettings, __unstableGetEditorMode, getTemporarilyEditingAsBlocks, + isTyping, } = unlock( select( blockEditorStore ) ); const { outlineMode, focusMode } = getSettings(); return { - isOutlineMode: outlineMode, + isOutlineMode: outlineMode && ! isTyping(), isFocusMode: focusMode, editorMode: __unstableGetEditorMode(), temporarilyEditingAsBlocks: getTemporarilyEditingAsBlocks(), diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index b91b9d32f52acc..ebd36bbc2e970d 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -86,7 +86,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { blockTitle, isSelected, isSubtreeDisabled, - isOutlineEnabled, hasOverlay, initialPosition, blockEditingMode, @@ -96,7 +95,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isReusable, isDragging, hasChildSelected, - removeOutline, isBlockMovingMode, canInsertMovingBlock, isEditingDisabled, @@ -116,7 +114,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { useFocusHandler( clientId ), useEventHandlers( { clientId, isSelected } ), useNavModeExit( clientId ), - useIsHovered( { isEnabled: isOutlineEnabled } ), + useIsHovered(), useIntersectionObserver(), useMovingAnimation( { triggerAnimationOnChange: index, clientId } ), useDisabled( { isDisabled: ! hasOverlay } ), @@ -179,7 +177,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { 'is-reusable': isReusable, 'is-dragging': isDragging, 'has-child-selected': hasChildSelected, - 'remove-outline': removeOutline, 'is-block-moving-mode': isBlockMovingMode, 'can-insert-moving-block': canInsertMovingBlock, 'is-editing-disabled': isEditingDisabled, diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-is-hovered.js b/packages/block-editor/src/components/block-list/use-block-props/use-is-hovered.js index 08c42eb1fdb085..518ed583933acd 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-is-hovered.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-is-hovered.js @@ -18,22 +18,17 @@ function listener( event ) { * Adds `is-hovered` class when the block is hovered and in navigation or * outline mode. */ -export function useIsHovered( { isEnabled } ) { - return useRefEffect( - ( node ) => { - if ( isEnabled ) { - node.addEventListener( 'mouseout', listener ); - node.addEventListener( 'mouseover', listener ); +export function useIsHovered() { + return useRefEffect( ( node ) => { + node.addEventListener( 'mouseout', listener ); + node.addEventListener( 'mouseover', listener ); - return () => { - node.removeEventListener( 'mouseout', listener ); - node.removeEventListener( 'mouseover', listener ); + return () => { + node.removeEventListener( 'mouseout', listener ); + node.removeEventListener( 'mouseover', listener ); - // Remove class in case it lingers. - node.classList.remove( 'is-hovered' ); - }; - } - }, - [ isEnabled ] - ); + // Remove class in case it lingers. + node.classList.remove( 'is-hovered' ); + }; + }, [] ); } From 7846a1afc3a11ffb2dd9de97ac2f7390e32f7d1b Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+sunil25393@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:22:25 +0530 Subject: [PATCH 25/97] Add Documentation for CharacterCount component (#60906) Co-authored-by: sunil25393 Co-authored-by: youknowriad --- packages/editor/README.md | 6 +++++- packages/editor/src/components/character-count/index.js | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index 17b9f97e3b6920..7e81e4ec8d8107 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -210,7 +210,11 @@ _Parameters_ ### CharacterCount -Undocumented declaration. +Renders the character count of the post content. + +_Returns_ + +- `number`: The character count. ### cleanForSlug diff --git a/packages/editor/src/components/character-count/index.js b/packages/editor/src/components/character-count/index.js index e12ff1a3cbce7a..2fb165cdc9badb 100644 --- a/packages/editor/src/components/character-count/index.js +++ b/packages/editor/src/components/character-count/index.js @@ -9,6 +9,11 @@ import { count as characterCount } from '@wordpress/wordcount'; */ import { store as editorStore } from '../../store'; +/** + * Renders the character count of the post content. + * + * @return {number} The character count. + */ export default function CharacterCount() { const content = useSelect( ( select ) => select( editorStore ).getEditedPostAttribute( 'content' ), From 6a0120b8c70de073cdf9db11066c3b0f500f2c64 Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+sunil25393@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:23:18 +0530 Subject: [PATCH 26/97] Add documentation for PostAuthor, PostAuthorCheck, PostAuthorPanel components (#61090) Co-authored-by: sunil25393 Co-authored-by: youknowriad --- packages/editor/README.md | 23 ++++++++++++++++--- .../src/components/post-author/check.js | 9 ++++++++ .../src/components/post-author/index.js | 5 ++++ .../src/components/post-author/panel.js | 5 ++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index 7e81e4ec8d8107..673d3d100a956e 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -869,15 +869,32 @@ _Returns_ ### PostAuthor -Undocumented declaration. +Renders the component for selecting the post author. + +_Returns_ + +- `Component`: The component to be rendered. ### PostAuthorCheck -Undocumented declaration. +Wrapper component that renders its children only if the post type supports the author. + +_Parameters_ + +- _props_ `Object`: The component props. +- _props.children_ `Element`: Children to be rendered. + +_Returns_ + +- `Component|null`: The component to be rendered. Return `null` if the post type doesn't supports the author or if there are no authors available. ### PostAuthorPanel -Undocumented declaration. +Renders the Post Author Panel component. + +_Returns_ + +- `Component`: The component to be rendered. ### PostComments diff --git a/packages/editor/src/components/post-author/check.js b/packages/editor/src/components/post-author/check.js index ecda9e149b4ed8..d10a0a2ccf0bd0 100644 --- a/packages/editor/src/components/post-author/check.js +++ b/packages/editor/src/components/post-author/check.js @@ -11,6 +11,15 @@ import PostTypeSupportCheck from '../post-type-support-check'; import { store as editorStore } from '../../store'; import { AUTHORS_QUERY } from './constants'; +/** + * Wrapper component that renders its children only if the post type supports the author. + * + * @param {Object} props The component props. + * @param {Element} props.children Children to be rendered. + * + * @return {Component|null} The component to be rendered. Return `null` if the post type doesn't + * supports the author or if there are no authors available. + */ export default function PostAuthorCheck( { children } ) { const { hasAssignAuthorAction, hasAuthors } = useSelect( ( select ) => { const post = select( editorStore ).getCurrentPost(); diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 7e734379f64f64..9ff3aaaf09a29c 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -13,6 +13,11 @@ import { AUTHORS_QUERY } from './constants'; const minimumUsersForCombobox = 25; +/** + * Renders the component for selecting the post author. + * + * @return {Component} The component to be rendered. + */ function PostAuthor() { const showCombobox = useSelect( ( select ) => { const authors = select( coreStore ).getUsers( AUTHORS_QUERY ); diff --git a/packages/editor/src/components/post-author/panel.js b/packages/editor/src/components/post-author/panel.js index 78f0e0a5f2cc89..ad2aa01dee3ab2 100644 --- a/packages/editor/src/components/post-author/panel.js +++ b/packages/editor/src/components/post-author/panel.js @@ -5,6 +5,11 @@ import PostAuthorCheck from './check'; import PostAuthorForm from './index'; import PostPanelRow from '../post-panel-row'; +/** + * Renders the Post Author Panel component. + * + * @return {Component} The component to be rendered. + */ export function PostAuthor() { return ( From b7c633cb2321039a047aa5cd25519f3fab859001 Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+sunil25393@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:24:27 +0530 Subject: [PATCH 27/97] Added doc for components PageAttributesCheck, PageAttributesPanel, PageAttributesOrder, PageAttributesParent (#60977) Co-authored-by: sunil25393 Co-authored-by: youknowriad --- packages/editor/README.md | 29 ++++++++++++++++--- .../src/components/page-attributes/check.js | 8 +++++ .../src/components/page-attributes/order.js | 6 ++++ .../src/components/page-attributes/panel.js | 5 ++++ .../src/components/page-attributes/parent.js | 6 ++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index 673d3d100a956e..5c363c27058f36 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -444,19 +444,40 @@ _Parameters_ ### PageAttributesCheck -Undocumented declaration. +Wrapper component that renders its children only if the post type supports page attributes. + +_Parameters_ + +- _props_ `Object`: - The component props. +- _props.children_ `Element`: - The child components to render. + +_Returns_ + +- `Component|null`: The rendered child components or null if page attributes are not supported. ### PageAttributesOrder -Undocumented declaration. +Renders the Page Attributes Order component. A number input in an editor interface for setting the order of a given page. + +_Returns_ + +- `Component`: The component to be rendered. ### PageAttributesPanel -Undocumented declaration. +Renders the Page Attributes Panel component. + +_Returns_ + +- `Component`: The component to be rendered. ### PageAttributesParent -Undocumented declaration. +Renders the Page Attributes Parent component. A dropdown menu in an editor interface for selecting the parent page of a given page. + +_Returns_ + +- `Component|null`: The component to be rendered. Return null if post type is not hierarchical. ### PageTemplate diff --git a/packages/editor/src/components/page-attributes/check.js b/packages/editor/src/components/page-attributes/check.js index 828b91b13f595f..bed2b1a353842a 100644 --- a/packages/editor/src/components/page-attributes/check.js +++ b/packages/editor/src/components/page-attributes/check.js @@ -9,6 +9,14 @@ import { store as coreStore } from '@wordpress/core-data'; */ import { store as editorStore } from '../../store'; +/** + * Wrapper component that renders its children only if the post type supports page attributes. + * + * @param {Object} props - The component props. + * @param {Element} props.children - The child components to render. + * + * @return {Component|null} The rendered child components or null if page attributes are not supported. + */ export function PageAttributesCheck( { children } ) { const supportsPageAttributes = useSelect( ( select ) => { const { getEditedPostAttribute } = select( editorStore ); diff --git a/packages/editor/src/components/page-attributes/order.js b/packages/editor/src/components/page-attributes/order.js index 4a751c0b151aba..d8a22f0cc949fb 100644 --- a/packages/editor/src/components/page-attributes/order.js +++ b/packages/editor/src/components/page-attributes/order.js @@ -53,6 +53,12 @@ function PageAttributesOrder() { ); } +/** + * Renders the Page Attributes Order component. A number input in an editor interface + * for setting the order of a given page. + * + * @return {Component} The component to be rendered. + */ export default function PageAttributesOrderWithChecks() { return ( diff --git a/packages/editor/src/components/page-attributes/panel.js b/packages/editor/src/components/page-attributes/panel.js index d6fb9294748ab0..5fb2638b517310 100644 --- a/packages/editor/src/components/page-attributes/panel.js +++ b/packages/editor/src/components/page-attributes/panel.js @@ -52,6 +52,11 @@ function AttributesPanel() { ); } +/** + * Renders the Page Attributes Panel component. + * + * @return {Component} The component to be rendered. + */ export default function PageAttributesPanel() { return ( diff --git a/packages/editor/src/components/page-attributes/parent.js b/packages/editor/src/components/page-attributes/parent.js index b1f9e15ff45e87..900f6f8a7c66b2 100644 --- a/packages/editor/src/components/page-attributes/parent.js +++ b/packages/editor/src/components/page-attributes/parent.js @@ -40,6 +40,12 @@ export const getItemPriority = ( name, searchValue ) => { return Infinity; }; +/** + * Renders the Page Attributes Parent component. A dropdown menu in an editor interface + * for selecting the parent page of a given page. + * + * @return {Component|null} The component to be rendered. Return null if post type is not hierarchical. + */ export function PageAttributesParent() { const { editPost } = useDispatch( editorStore ); const [ fieldValue, setFieldValue ] = useState( false ); From 3ad84197cad8737fbf9d37fae521564e02502965 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 26 Apr 2024 13:55:06 +0100 Subject: [PATCH 28/97] ToolsMoreMenuGroup: Remove form post editor (#61132) Co-authored-by: youknowriad Co-authored-by: Mamaduka --- .../header/tools-more-menu-group/index.js | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 packages/edit-post/src/components/header/tools-more-menu-group/index.js diff --git a/packages/edit-post/src/components/header/tools-more-menu-group/index.js b/packages/edit-post/src/components/header/tools-more-menu-group/index.js deleted file mode 100644 index b7ff7b8ce25f22..00000000000000 --- a/packages/edit-post/src/components/header/tools-more-menu-group/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * WordPress dependencies - */ -import { createSlotFill, MenuGroup } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -const { Fill: ToolsMoreMenuGroup, Slot } = - createSlotFill( 'ToolsMoreMenuGroup' ); - -ToolsMoreMenuGroup.Slot = ( { fillProps } ) => ( - - { ( fills ) => - fills.length > 0 && ( - { fills } - ) - } - -); - -export default ToolsMoreMenuGroup; From ff01bcbd4aeb84d6a28a655ca652a9ad9f16cebe Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:14:03 +0200 Subject: [PATCH 29/97] Unify placeholders (#59275) * Draft/Experiment: Explore unified placeholders. * Remove comments. * Update label. * Update hover styles. * Update changelog. * Use 2px consistent icons across group and columns * Use : in variation instructions * Add target for hover * Show title. * Initial layout work. * Improve behavior for narrow contexts. * Polish. * Apply margin to Group as well. * Center the buttons when small. * Center upload, 4px padding. * Address feedback (4px gap, justification) * Update embed block. * Fix site logo centering, improve very small contexts. --------- Co-authored-by: Rich Tabor --- .../block-variation-picker/content.scss | 94 +++++-------------- .../block-variation-picker/index.js | 5 +- .../components/media-placeholder/content.scss | 11 --- .../src/components/media-placeholder/index.js | 6 +- packages/block-library/src/columns/edit.js | 1 + .../block-library/src/columns/variations.js | 46 +++------ packages/block-library/src/embed/editor.scss | 2 - .../src/embed/embed-placeholder.js | 30 ++++-- packages/block-library/src/group/editor.scss | 47 ---------- .../block-library/src/group/placeholder.js | 36 +++---- .../src/post-featured-image/editor.scss | 1 + .../block-library/src/site-logo/editor.scss | 1 + packages/components/CHANGELOG.md | 1 + .../components/src/placeholder/style.scss | 46 +++------ 14 files changed, 95 insertions(+), 232 deletions(-) diff --git a/packages/block-editor/src/components/block-variation-picker/content.scss b/packages/block-editor/src/components/block-variation-picker/content.scss index 882e2748f6c92c..e4636f288078d3 100644 --- a/packages/block-editor/src/components/block-variation-picker/content.scss +++ b/packages/block-editor/src/components/block-variation-picker/content.scss @@ -1,89 +1,39 @@ -.block-editor-block-variation-picker { - .components-placeholder__instructions { - // Defer to vertical margins applied by template picker options. - margin-bottom: 0; - } - - .components-placeholder__fieldset { - // Options will render horizontally, but the immediate children of the - // fieldset are the options and the skip button, oriented vertically. - flex-direction: column; - } - - &.has-many-variations .components-placeholder__fieldset { - // Allow options to occupy a greater amount of the available space if - // many options exist. - max-width: 90%; - } -} - -.block-editor-block-variation-picker__variations.block-editor-block-variation-picker__variations { +.block-editor-block-variation-picker__variations, +.block-editor-block-variation-picker__skip, +.wp-block-group-placeholder__variations { + list-style: none; display: flex; justify-content: flex-start; flex-direction: row; flex-wrap: wrap; width: 100%; - margin: $grid-unit-20 0; padding: 0; - list-style: none; + margin: 0; + gap: $grid-unit-10; + font-size: $helptext-font-size; - > li { - list-style: none; - margin: $grid-unit-10 ( $grid-unit-10 + $grid-unit-15 ) 0 0; - flex-shrink: 1; - width: 75px; - text-align: center; - - button { - display: inline-flex; - margin-right: 0; - } + svg { + fill: $gray-400 !important; } - .block-editor-block-variation-picker__variation { - padding: $grid-unit-10; + .components-button { + padding: 4px; } - .block-editor-block-variation-picker__variation-label { - font-family: $default-font; - font-size: 12px; - display: block; - line-height: 1.4; + .components-button:hover { + background: none !important; // !important to simplify the selector. } -} -.block-editor-block-variation-picker__variation { - width: 100%; - - &.components-button.has-icon { - // Override default styles inherited from @@ -422,7 +422,7 @@ export function MediaPlaceholder( { @@ -435,7 +435,7 @@ export function MediaPlaceholder( { const defaultButton = ( { open } ) => { return (
{ cannotEmbed && ( -
+
{ __( 'Sorry, this content could not be embedded.' ) }
- { ' ' } - -
+ + { ' ' } + + + ) } ); diff --git a/packages/block-library/src/group/editor.scss b/packages/block-library/src/group/editor.scss index 7ea432f30f0168..076d7a18810853 100644 --- a/packages/block-library/src/group/editor.scss +++ b/packages/block-library/src/group/editor.scss @@ -53,50 +53,3 @@ } } -.wp-block-group__placeholder { - .wp-block-group-placeholder__variations { - list-style: none; - display: flex; - justify-content: center; - flex-direction: row; - flex-wrap: wrap; - width: 100%; - padding: 0; - margin: 0; - } - .components-placeholder__instructions { - margin-bottom: 18px; - } - .wp-block-group-placeholder__variations svg { - fill: $gray-400 !important; - } - .wp-block-group-placeholder__variations svg:hover { - fill: var(--wp-admin-theme-color) !important; - } - .wp-block-group-placeholder__variations > li { - margin: 0 $grid-unit-15 $grid-unit-15 $grid-unit-15; - width: auto; - display: flex; - flex-direction: column; - align-items: center; - } - .wp-block-group-placeholder__variations li > .wp-block-group-placeholder__variation-button { - width: 44px; - height: 32px; - padding: 0; - &:hover { - box-shadow: none; - } - } - .components-placeholder { - min-height: auto; - padding: $grid-unit-30; - align-items: center; - } - .is-small, - .is-medium { - .wp-block-group-placeholder__variations > li { - margin: $grid-unit-15; - } - } -} diff --git a/packages/block-library/src/group/placeholder.js b/packages/block-library/src/group/placeholder.js index 16a99a9287338d..ed3e53c3eb2b9c 100644 --- a/packages/block-library/src/group/placeholder.js +++ b/packages/block-library/src/group/placeholder.js @@ -20,42 +20,41 @@ const getGroupPlaceholderIcons = ( name = 'group' ) => { group: ( - + ), 'group-row': ( - + ), 'group-stack': ( - + ), 'group-grid': ( - - + ), }; @@ -159,11 +158,12 @@ function GroupPlaceHolder( { name, onSelect } ) { { variations.map( ( variation ) => (
  • - + { blockInformation.anchor } + + + ) } + { positionLabel && isSticky && ( + + + + ) } + { images.length ? ( + + { images.map( ( image, index ) => ( + + ) ) } + + ) : null } + { isLocked && ( + + + + ) } + + ); } diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 658188dbd0fc70..bb34c4f61b4ea4 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -22,7 +22,9 @@ import { } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { ESCAPE } from '@wordpress/keycodes'; +import { BACKSPACE, DELETE, ESCAPE } from '@wordpress/keycodes'; +import isShallowEqual from '@wordpress/is-shallow-equal'; +import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -44,6 +46,7 @@ import { store as blockEditorStore } from '../../store'; import useBlockDisplayInformation from '../use-block-display-information'; import { useBlockLock } from '../block-lock'; import AriaReferencedText from './aria-referenced-text'; +import { unlock } from '../../lock-unlock'; function ListViewBlock( { block: { clientId }, @@ -78,7 +81,26 @@ function ListViewBlock( { isSelected && selectedClientIds[ selectedClientIds.length - 1 ] === clientId; - const { toggleBlockHighlight } = useDispatch( blockEditorStore ); + const { + toggleBlockHighlight, + duplicateBlocks, + multiSelect, + removeBlocks, + insertAfterBlock, + insertBeforeBlock, + setOpenedBlockSettingsMenu, + } = unlock( useDispatch( blockEditorStore ) ); + + const { + canInsertBlockType, + getSelectedBlockClientIds, + getPreviousBlockClientId, + getBlockRootClientId, + getBlockOrder, + getBlockParents, + getBlocksByClientId, + canRemoveBlocks, + } = useSelect( blockEditorStore ); const blockInformation = useBlockDisplayInformation( clientId ); @@ -116,26 +138,191 @@ function ListViewBlock( { const { expand, collapse, + collapseAll, BlockSettingsMenu, listViewInstanceId, expandedState, setInsertedBlock, treeGridElementRef, + rootClientId, } = useListViewContext(); + const isMatch = useShortcutEventMatch(); + + // Determine which blocks to update: + // If the current (focused) block is part of the block selection, use the whole selection. + // If the focused block is not part of the block selection, only update the focused block. + function getBlocksToUpdate() { + const selectedBlockClientIds = getSelectedBlockClientIds(); + const isUpdatingSelectedBlocks = + selectedBlockClientIds.includes( clientId ); + const firstBlockClientId = isUpdatingSelectedBlocks + ? selectedBlockClientIds[ 0 ] + : clientId; + const firstBlockRootClientId = + getBlockRootClientId( firstBlockClientId ); + + const blocksToUpdate = isUpdatingSelectedBlocks + ? selectedBlockClientIds + : [ clientId ]; - // If multiple blocks are selected, deselect all blocks when the user - // presses the escape key. - const onKeyDown = ( event ) => { - if ( - event.keyCode === ESCAPE && - ! event.defaultPrevented && - selectedClientIds.length > 0 - ) { + return { + blocksToUpdate, + firstBlockClientId, + firstBlockRootClientId, + selectedBlockClientIds, + }; + } + + /** + * @param {KeyboardEvent} event + */ + async function onKeyDown( event ) { + if ( event.defaultPrevented ) { + return; + } + + // If multiple blocks are selected, deselect all blocks when the user + // presses the escape key. + if ( event.keyCode === ESCAPE && selectedClientIds.length > 0 ) { event.stopPropagation(); event.preventDefault(); selectBlock( event, undefined ); + } else if ( + event.keyCode === BACKSPACE || + event.keyCode === DELETE || + isMatch( 'core/block-editor/remove', event ) + ) { + const { + blocksToUpdate: blocksToDelete, + firstBlockClientId, + firstBlockRootClientId, + selectedBlockClientIds, + } = getBlocksToUpdate(); + + // Don't update the selection if the blocks cannot be deleted. + if ( ! canRemoveBlocks( blocksToDelete, firstBlockRootClientId ) ) { + return; + } + + let blockToFocus = + getPreviousBlockClientId( firstBlockClientId ) ?? + // If the previous block is not found (when the first block is deleted), + // fallback to focus the parent block. + firstBlockRootClientId; + + removeBlocks( blocksToDelete, false ); + + // Update the selection if the original selection has been removed. + const shouldUpdateSelection = + selectedBlockClientIds.length > 0 && + getSelectedBlockClientIds().length === 0; + + // If there's no previous block nor parent block, focus the first block. + if ( ! blockToFocus ) { + blockToFocus = getBlockOrder()[ 0 ]; + } + + updateFocusAndSelection( blockToFocus, shouldUpdateSelection ); + } else if ( isMatch( 'core/block-editor/duplicate', event ) ) { + event.preventDefault(); + + const { blocksToUpdate, firstBlockRootClientId } = + getBlocksToUpdate(); + + const canDuplicate = getBlocksByClientId( blocksToUpdate ).every( + ( blockToUpdate ) => { + return ( + !! blockToUpdate && + hasBlockSupport( + blockToUpdate.name, + 'multiple', + true + ) && + canInsertBlockType( + blockToUpdate.name, + firstBlockRootClientId + ) + ); + } + ); + + if ( canDuplicate ) { + const updatedBlocks = await duplicateBlocks( + blocksToUpdate, + false + ); + + if ( updatedBlocks?.length ) { + // If blocks have been duplicated, focus the first duplicated block. + updateFocusAndSelection( updatedBlocks[ 0 ], false ); + } + } + } else if ( isMatch( 'core/block-editor/insert-before', event ) ) { + event.preventDefault(); + + const { blocksToUpdate } = getBlocksToUpdate(); + await insertBeforeBlock( blocksToUpdate[ 0 ] ); + const newlySelectedBlocks = getSelectedBlockClientIds(); + + // Focus the first block of the newly inserted blocks, to keep focus within the list view. + setOpenedBlockSettingsMenu( undefined ); + updateFocusAndSelection( newlySelectedBlocks[ 0 ], false ); + } else if ( isMatch( 'core/block-editor/insert-after', event ) ) { + event.preventDefault(); + + const { blocksToUpdate } = getBlocksToUpdate(); + await insertAfterBlock( blocksToUpdate.at( -1 ) ); + const newlySelectedBlocks = getSelectedBlockClientIds(); + + // Focus the first block of the newly inserted blocks, to keep focus within the list view. + setOpenedBlockSettingsMenu( undefined ); + updateFocusAndSelection( newlySelectedBlocks[ 0 ], false ); + } else if ( isMatch( 'core/block-editor/select-all', event ) ) { + event.preventDefault(); + + const { firstBlockRootClientId, selectedBlockClientIds } = + getBlocksToUpdate(); + const blockClientIds = getBlockOrder( firstBlockRootClientId ); + if ( ! blockClientIds.length ) { + return; + } + + // If we have selected all sibling nested blocks, try selecting up a level. + // This is a similar implementation to that used by `useSelectAll`. + // `isShallowEqual` is used for the list view instead of a length check, + // as the array of siblings of the currently focused block may be a different + // set of blocks from the current block selection if the user is focused + // on a different part of the list view from the block selection. + if ( isShallowEqual( selectedBlockClientIds, blockClientIds ) ) { + // Only select up a level if the first block is not the root block. + // This ensures that the block selection can't break out of the root block + // used by the list view, if the list view is only showing a partial hierarchy. + if ( + firstBlockRootClientId && + firstBlockRootClientId !== rootClientId + ) { + updateFocusAndSelection( firstBlockRootClientId, true ); + return; + } + } + + // Select all while passing `null` to skip focusing to the editor canvas, + // and retain focus within the list view. + multiSelect( + blockClientIds[ 0 ], + blockClientIds[ blockClientIds.length - 1 ], + null + ); + } else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) { + event.preventDefault(); + const { firstBlockClientId } = getBlocksToUpdate(); + const blockParents = getBlockParents( firstBlockClientId, false ); + // Collapse all blocks. + collapseAll(); + // Expand all parents of the current block. + expand( blockParents ); } - }; + } const onMouseEnter = useCallback( () => { setIsHovered( true ); @@ -346,7 +533,6 @@ function ListViewBlock( { isExpanded={ canEdit ? isExpanded : undefined } selectedClientIds={ selectedClientIds } ariaDescribedBy={ descriptionId } - updateFocusAndSelection={ updateFocusAndSelection } /> { `${ blockPositionDescription } ${ blockPropertiesDescription }` } From 83737c18399cbbe456fc7d275c61002e45df7737 Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Mon, 29 Apr 2024 22:44:57 -0700 Subject: [PATCH 53/97] =?UTF-8?q?Fix=20import=20in=20block=20editor?= =?UTF-8?q?=E2=80=99s=20readme=20example=20(#61218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `WritingFlow` import was unused and `BlockCanvas` import was missing. Co-authored-by: stokesman Co-authored-by: Mamaduka --- packages/block-editor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 154ea248ae6518..6ac825da4925e6 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -17,9 +17,9 @@ _This package assumes that your code will run in an **ES2015+** environment. If ```js import { useState } from 'react'; import { + BlockCanvas, BlockEditorProvider, BlockList, - WritingFlow, } from '@wordpress/block-editor'; function MyEditorComponent() { From 8c90f60047a7702ee3872269dc20571fb583fe9a Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 30 Apr 2024 10:18:25 +0400 Subject: [PATCH 54/97] List View: Use 'isMatch' for unselect the shortcut (#61223) Co-authored-by: Mamaduka Co-authored-by: andrewserong --- packages/block-editor/src/components/list-view/block.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index bb34c4f61b4ea4..46ba4e536ee673 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -22,7 +22,7 @@ import { } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BACKSPACE, DELETE, ESCAPE } from '@wordpress/keycodes'; +import { BACKSPACE, DELETE } from '@wordpress/keycodes'; import isShallowEqual from '@wordpress/is-shallow-equal'; import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; @@ -183,7 +183,10 @@ function ListViewBlock( { // If multiple blocks are selected, deselect all blocks when the user // presses the escape key. - if ( event.keyCode === ESCAPE && selectedClientIds.length > 0 ) { + if ( + isMatch( 'core/block-editor/unselect', event ) && + selectedClientIds.length > 0 + ) { event.stopPropagation(); event.preventDefault(); selectBlock( event, undefined ); From 667ba29765f7e82c96b4a71ab801151370c641bc Mon Sep 17 00:00:00 2001 From: Juan Aldasoro Date: Tue, 30 Apr 2024 08:44:34 +0200 Subject: [PATCH 55/97] Notices: Fix snackbar placement (#60912) * Fix snackbar placement for distraction free, and when blocks breadcrumbs is enabled. * Add `has-block-breadcrumbs` class only when the preference is set and not in distraction-free mode. * Update conditionals in favor of readability. Co-authored-by: juanfra Co-authored-by: draganescu Co-authored-by: richtabor --- packages/edit-post/src/components/layout/index.js | 4 ++++ packages/edit-post/src/components/layout/style.scss | 11 +++++++++-- packages/edit-site/src/components/editor/style.scss | 7 ++++++- packages/edit-site/src/components/layout/index.js | 9 +++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 4db2c1ebf41e72..e814611744e83f 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -159,6 +159,7 @@ function Layout( { initialPost } ) { showMetaBoxes, documentLabel, hasHistory, + hasBlockBreadcrumbs, } = useSelect( ( select ) => { const { get } = select( preferencesStore ); const { getEditorSettings, getPostTypeLabel } = select( editorStore ); @@ -193,6 +194,7 @@ function Layout( { initialPost } ) { hasBlockSelected: !! select( blockEditorStore ).getBlockSelectionStart(), hasHistory: !! getEditorSettings().onNavigateToPreviousEntityRecord, + hasBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ), }; }, [] ); @@ -243,6 +245,8 @@ function Layout( { initialPost } ) { 'has-metaboxes': hasActiveMetaboxes, 'is-distraction-free': isDistractionFree && isWideViewport, 'is-entity-save-view-open': !! entitiesSavedStatesCallback, + 'has-block-breadcrumbs': + hasBlockBreadcrumbs && ! isDistractionFree && isWideViewport, } ); const secondarySidebarLabel = isListViewOpened diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 229ab58a4e14b0..e226e9c65a3e08 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -7,14 +7,21 @@ .edit-post-layout .components-editor-notices__snackbar { position: fixed; right: 0; - bottom: 40px; + bottom: 16px; padding-left: 16px; padding-right: 16px; } .is-distraction-free { .components-editor-notices__snackbar { - bottom: 20px; + bottom: 16px; + } +} + +// Adjust the position of the notices when breadcrumbs are present +.has-block-breadcrumbs { + .components-editor-notices__snackbar { + bottom: 40px; } } diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index 26403858ff22e0..8803c0c59ff5a6 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -22,11 +22,16 @@ justify-content: center; } +// Adjust the position of the notices when breadcrumbs are present +.edit-site .has-block-breadcrumbs.is-full-canvas .components-editor-notices__snackbar { + bottom: 40px; +} + // Adjust the position of the notices .edit-site .components-editor-notices__snackbar { position: absolute; right: 0; - bottom: 40px; + bottom: 16px; padding-left: 16px; padding-right: 16px; } diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index e497695e73e6b8..77c057bde24682 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -78,6 +78,7 @@ export default function Layout() { canvasMode, previousShortcut, nextShortcut, + hasBlockBreadcrumbs, } = useSelect( ( select ) => { const { getAllShortcutKeyCombinations } = select( keyboardShortcutsStore @@ -99,6 +100,10 @@ export default function Layout() { 'core', 'distractionFree' ), + hasBlockBreadcrumbs: select( preferencesStore ).get( + 'core', + 'showBlockBreadcrumbs' + ), isZoomOutMode: select( blockEditorStore ).__unstableGetEditorMode() === 'zoom-out', @@ -182,6 +187,10 @@ export default function Layout() { 'has-fixed-toolbar': hasFixedToolbar, 'is-block-toolbar-visible': hasBlockSelected, 'is-zoom-out': isZoomOutMode, + 'has-block-breadcrumbs': + hasBlockBreadcrumbs && + ! isDistractionFree && + canvasMode === 'edit', } ) } > From d0550832f4bf3faef8643e866641f369b2897990 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 30 Apr 2024 10:53:25 +0200 Subject: [PATCH 56/97] Remove deprecated util. (#61113) --- .../styles/alignment-matrix-control-styles.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts index 0df630181b388d..bdb015ec64c6a7 100644 --- a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts +++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts @@ -7,7 +7,7 @@ import { css } from '@emotion/react'; /** * Internal dependencies */ -import { COLORS, reduceMotion } from '../../utils'; +import { COLORS } from '../../utils'; import type { AlignmentMatrixControlProps, AlignmentMatrixControlCellProps, @@ -74,9 +74,10 @@ export const pointBase = ( box-sizing: border-box; display: grid; margin: auto; - transition: all 120ms linear; + @media not ( prefers-reduced-motion ) { + transition: all 120ms linear; + } - ${ reduceMotion( 'transition' ) } ${ pointActive( props ) } `; }; From bff764b1fe846098844cbfe83ea6feac51fba697 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 30 Apr 2024 10:54:02 +0200 Subject: [PATCH 57/97] Remove deprecated util. (#61116) --- .../focal-point-picker/styles/focal-point-picker-style.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts b/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts index 3ff8223e05e05c..71bf4b651b4ad7 100644 --- a/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts +++ b/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts @@ -9,7 +9,7 @@ import styled from '@emotion/styled'; */ import { Flex } from '../../flex'; import UnitControl from '../../unit-control'; -import { COLORS, CONFIG, reduceMotion } from '../../utils'; +import { COLORS, CONFIG } from '../../utils'; import type { FocalPointPickerControlsProps } from '../types'; import { INITIAL_BOUNDS } from '../utils'; @@ -106,10 +106,11 @@ export const GridView = styled.div` position: absolute; top: 50%; transform: translate3d( -50%, -50%, 0 ); - transition: opacity 100ms linear; z-index: 1; - ${ reduceMotion( 'transition' ) } + @media not ( prefers-reduced-motion ) { + transition: opacity 100ms linear; + } opacity: ${ ( { showOverlay }: { showOverlay?: boolean } ) => showOverlay ? 1 : 0 }; From c3d9d989cfbd739d3c0b52d4e3d2710588e12a66 Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 30 Apr 2024 10:55:11 +0200 Subject: [PATCH 58/97] Remove deprecated util. (#61117) --- .../components/src/navigation/styles/navigation-styles.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/src/navigation/styles/navigation-styles.tsx b/packages/components/src/navigation/styles/navigation-styles.tsx index 820cf2224be885..299a9b89bec709 100644 --- a/packages/components/src/navigation/styles/navigation-styles.tsx +++ b/packages/components/src/navigation/styles/navigation-styles.tsx @@ -15,7 +15,7 @@ import { COLORS } from '../../utils/colors-values'; import Button from '../../button'; import { Text } from '../../text'; import { Heading } from '../../heading'; -import { reduceMotion, rtl } from '../../utils'; +import { rtl } from '../../utils'; import { space } from '../../utils/space'; export const NavigationUI = styled.div` @@ -173,7 +173,6 @@ export const ItemBadgeUI = styled.span` display: inline-flex; padding: ${ space( 1 ) } ${ space( 3 ) }; border-radius: 2px; - animation: fade-in 250ms ease-out; @keyframes fade-in { from { @@ -184,7 +183,9 @@ export const ItemBadgeUI = styled.span` } } - ${ reduceMotion( 'animation' ) }; + @media not ( prefers-reduced-motion ) { + animation: fade-in 250ms ease-out; + } `; export const ItemTitleUI = styled( Text )` From 694447df6cbf84fe28de14f09880ffb7983273ad Mon Sep 17 00:00:00 2001 From: Dani Guardiola Date: Tue, 30 Apr 2024 10:58:35 +0200 Subject: [PATCH 59/97] Remove deprecated util. (#61120) --- .../toggle-group-control-option-base/styles.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts index 8885eae11d2c7c..c929af69414004 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts +++ b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts @@ -7,7 +7,7 @@ import styled from '@emotion/styled'; /** * Internal dependencies */ -import { CONFIG, COLORS, reduceMotion } from '../../utils'; +import { CONFIG, COLORS } from '../../utils'; import type { ToggleGroupControlProps, ToggleGroupControlOptionBaseProps, @@ -50,11 +50,12 @@ export const buttonView = ( { padding: 0 12px; position: relative; text-align: center; - transition: - background ${ CONFIG.transitionDurationFast } linear, - color ${ CONFIG.transitionDurationFast } linear, - font-weight 60ms linear; - ${ reduceMotion( 'transition' ) } + @media not ( prefers-reduced-motion ) { + transition: + background ${ CONFIG.transitionDurationFast } linear, + color ${ CONFIG.transitionDurationFast } linear, + font-weight 60ms linear; + } user-select: none; width: 100%; z-index: 2; From 7fd46f2d2e2095253be5ee1d680210670dc578cb Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:17:29 +0900 Subject: [PATCH 60/97] Fix: Pattern details page backpath (#61174) * Fix: Pattern details page backpath * Add e2e test Co-authored-by: t-hamano Co-authored-by: jsnajdr --- .../index.js | 14 ++++++---- .../site-editor-url-navigation.spec.js | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js index 8b459e3ea6a91a..ec80b7054c6db4 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js @@ -23,7 +23,7 @@ export default function SidebarNavigationScreenPattern() { const history = useHistory(); const location = useLocation(); const { - params: { postType, postId }, + params: { postType, postId, categoryId, categoryType }, } = location; const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); @@ -48,10 +48,14 @@ export default function SidebarNavigationScreenPattern() { * Depending on whether the theme supports block-template-parts, we go back to Patterns or Template screens. * This is temporary. We aim to consolidate to /patterns. */ - const backPath = - isTemplatePartsMode && postType === 'wp_template_part' - ? { path: '/wp_template_part/all' } - : { path: '/patterns' }; + const backPath = { + categoryId, + categoryType, + path: + isTemplatePartsMode && postType === 'wp_template_part' + ? '/wp_template_part/all' + : '/patterns', + }; return ( { '/wp-admin/site-editor.php?postId=emptytheme%2F%2Fdemo&postType=wp_template_part&canvas=edit' ); } ); + + test( 'The Patterns page should keep the previously selected template part category', async ( { + admin, + page, + } ) => { + await admin.visitSiteEditor(); + const navigation = page.getByRole( 'region', { + name: 'Navigation', + } ); + await navigation.getByRole( 'button', { name: 'Patterns' } ).click(); + await navigation.getByRole( 'button', { name: 'General' } ).click(); + await page + .getByRole( 'region', { + name: 'Patterns content', + } ) + .getByLabel( 'header', { exact: true } ) + .click(); + await expect( + page.getByRole( 'region', { name: 'Editor content' } ) + ).toBeVisible(); + await page.getByRole( 'button', { name: 'Open navigation' } ).click(); + await navigation.getByRole( 'button', { name: 'Back' } ).click(); + await expect( + navigation.getByRole( 'button', { name: 'General' } ) + ).toHaveAttribute( 'aria-current', 'true' ); + } ); } ); From e2142c3e6a7562ef9e9e059f8c85a9c6791212f6 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:45:14 +0900 Subject: [PATCH 61/97] Font Library: Convert heading text to heading elements and group fonts as a list (#58834) * temp * Font Library: Convert heading text to heading elements and group fonts as a list Co-authored-by: t-hamano Co-authored-by: afercia Co-authored-by: colorful-tones Co-authored-by: jasmussen Co-authored-by: annezazu --- .../font-library-modal/font-collection.js | 43 ++++-- .../font-library-modal/installed-fonts.js | 136 +++++++++++------- .../font-library-modal/style.scss | 17 +++ .../specs/site-editor/font-library.spec.js | 2 +- 4 files changed, 131 insertions(+), 67 deletions(-) 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 1a1853ac62a381..7d89a92cb44bac 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 @@ -332,18 +332,37 @@ function FontCollection( { slug } ) { ) }
    - { items.map( ( font ) => ( - { - setSelectedFont( - font.font_family_settings - ); - } } - /> - ) ) } + { /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ } +
      + { items.map( ( font ) => ( +
    • + { + setSelectedFont( + font.font_family_settings + ); + } } + /> +
    • + ) ) } +
    + { /* eslint-enable jsx-a11y/no-redundant-roles */ }{ ' ' }
    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 b33bb1b639d000..cdc81aa09b7373 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 @@ -126,66 +126,94 @@ function InstalledFonts() { } > - { notice && ( - <> - + + { notice && ( setNotice( null ) } > { notice.message } - - - ) } - { baseCustomFonts.length > 0 && ( - <> - - { __( 'Installed Fonts' ) } - - - { baseCustomFonts.map( ( font ) => ( - { - handleSetLibraryFontSelected( - font - ); - } } - /> - ) ) } - - - ) } - - { baseThemeFonts.length > 0 && ( - <> - - { __( 'Theme Fonts' ) } - - - { baseThemeFonts.map( ( font ) => ( - { - handleSetLibraryFontSelected( - font - ); - } } - /> - ) ) } - - ) } + ) } + { baseCustomFonts.length > 0 && ( + +

    + { __( 'Installed Fonts' ) } +

    + { /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ } +
      + { baseCustomFonts.map( ( font ) => ( +
    • + { + handleSetLibraryFontSelected( + font + ); + } } + /> +
    • + ) ) } +
    + { /* eslint-enable jsx-a11y/no-redundant-roles */ } +
    + ) } + { baseThemeFonts.length > 0 && ( + +

    + { __( 'Theme Fonts' ) } +

    + { /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ } +
      + { baseThemeFonts.map( ( font ) => ( +
    • + { + handleSetLibraryFontSelected( + font + ); + } } + /> +
    • + ) ) } +
    + { /* eslint-enable jsx-a11y/no-redundant-roles */ } +
    + ) } +
    diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss index 201d4d87a3fb16..fb87f6fae505a4 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss +++ b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss @@ -66,6 +66,23 @@ $footer-height: 70px; margin-bottom: 0; } +.font-library-modal__fonts-title { + text-transform: uppercase; + font-size: 11px; + font-weight: 600; + margin-top: 0; + margin-bottom: 0; +} + +.font-library-modal__fonts-list { + margin-top: 0; + margin-bottom: 0; +} + +.font-library-modal__fonts-list-item { + margin-bottom: 0; +} + .font-library-modal__font-card { border: 1px solid $gray-200; width: 100%; diff --git a/test/e2e/specs/site-editor/font-library.spec.js b/test/e2e/specs/site-editor/font-library.spec.js index 986b400c0d6e5a..2601f66b3e0787 100644 --- a/test/e2e/specs/site-editor/font-library.spec.js +++ b/test/e2e/specs/site-editor/font-library.spec.js @@ -61,7 +61,7 @@ test.describe( 'Font Library', () => { .click(); await expect( page.getByRole( 'dialog' ) ).toBeVisible(); await expect( - page.getByRole( 'heading', { name: 'Fonts' } ) + page.getByRole( 'heading', { name: 'Fonts', exact: true } ) ).toBeVisible(); } ); From 0cb3850424fe66e3ea4ccd9134739b869ebd905c Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:18:19 +0200 Subject: [PATCH 62/97] Components: Fix snapshot tests of ToggleGroupControl (#61228) Co-authored-by: tyxla Co-authored-by: Mamaduka --- .../test/__snapshots__/index.tsx.snap | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index 5b4322102fa32d..edd203a4321998 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -107,8 +107,6 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = padding: 0 12px; position: relative; text-align: center; - -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; - transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -123,9 +121,10 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = color: #fff; } -@media ( prefers-reduced-motion: reduce ) { +@media not ( prefers-reduced-motion ) { .emotion-12 { - transition-duration: 0ms; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; } } @@ -190,8 +189,6 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = padding: 0 12px; position: relative; text-align: center; - -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; - transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -205,9 +202,10 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = padding-right: 0; } -@media ( prefers-reduced-motion: reduce ) { +@media not ( prefers-reduced-motion ) { .emotion-18 { - transition-duration: 0ms; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; } } @@ -436,8 +434,6 @@ exports[`ToggleGroupControl controlled should render correctly with text options padding: 0 12px; position: relative; text-align: center; - -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; - transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -446,9 +442,10 @@ exports[`ToggleGroupControl controlled should render correctly with text options z-index: 2; } -@media ( prefers-reduced-motion: reduce ) { +@media not ( prefers-reduced-motion ) { .emotion-12 { - transition-duration: 0ms; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; } } @@ -656,8 +653,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] padding: 0 12px; position: relative; text-align: center; - -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; - transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -672,9 +667,10 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] color: #fff; } -@media ( prefers-reduced-motion: reduce ) { +@media not ( prefers-reduced-motion ) { .emotion-12 { - transition-duration: 0ms; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; } } @@ -739,8 +735,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] padding: 0 12px; position: relative; text-align: center; - -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; - transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -754,9 +748,10 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] padding-right: 0; } -@media ( prefers-reduced-motion: reduce ) { +@media not ( prefers-reduced-motion ) { .emotion-18 { - transition-duration: 0ms; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; } } @@ -979,8 +974,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio padding: 0 12px; position: relative; text-align: center; - -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; - transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -989,9 +982,10 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio z-index: 2; } -@media ( prefers-reduced-motion: reduce ) { +@media not ( prefers-reduced-motion ) { .emotion-12 { - transition-duration: 0ms; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; } } From d504dd1457f9e6346f08e0d0511fea359eab6339 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 30 Apr 2024 15:10:07 +0400 Subject: [PATCH 63/97] Upgrade React to v18.3 (#61202) Co-authored-by: Mamaduka Co-authored-by: tyxla Co-authored-by: youknowriad Co-authored-by: derekblank --- package-lock.json | 124 +++++++++--------- package.json | 8 +- packages/babel-preset-default/package.json | 2 +- packages/blocks/package.json | 2 +- .../tooltip/index.native.js | 8 +- packages/element/package.json | 4 +- platform-docs/package.json | 4 +- 7 files changed, 73 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3979cee9eed1cd..f534147e67aaa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "@storybook/source-loader": "7.6.15", "@storybook/theming": "7.6.15", "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "14.0.0", + "@testing-library/react": "14.3.0", "@testing-library/react-native": "12.4.3", "@testing-library/user-event": "14.4.3", "@types/eslint": "8.56.9", @@ -219,12 +219,12 @@ "postcss-loader": "6.2.1", "prettier": "npm:wp-prettier@3.0.3", "progress": "2.0.3", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "react-native": "0.73.3", "react-native-url-polyfill": "1.1.2", "react-refresh": "0.14.0", - "react-test-renderer": "18.2.0", + "react-test-renderer": "18.3.1", "reassure": "0.7.1", "redux": "4.1.2", "resize-observer-polyfill": "1.5.1", @@ -15310,9 +15310,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", - "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.0.tgz", + "integrity": "sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -43802,9 +43802,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -43908,15 +43908,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-element-to-jsx-string": { @@ -44502,23 +44502,23 @@ } }, "node_modules/react-test-renderer": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", "dev": true, "dependencies": { - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-test-renderer/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, "node_modules/read": { @@ -46120,9 +46120,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -53118,7 +53118,7 @@ "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", - "react": "^18.2.0" + "react": "^18.3.0" }, "engines": { "node": ">=14" @@ -53404,7 +53404,7 @@ "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "react-is": "^18.2.0", + "react-is": "^18.3.0", "remove-accents": "^0.5.0", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", @@ -53418,9 +53418,9 @@ } }, "packages/blocks/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "packages/blocks/node_modules/uuid": { "version": "8.3.2", @@ -54230,8 +54230,8 @@ "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" }, "engines": { "node": ">=12" @@ -66302,9 +66302,9 @@ } }, "@testing-library/react": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", - "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.0.tgz", + "integrity": "sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", @@ -68490,7 +68490,7 @@ "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", - "react": "^18.2.0" + "react": "^18.3.0" } }, "@wordpress/base-styles": { @@ -68702,7 +68702,7 @@ "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "react-is": "^18.2.0", + "react-is": "^18.3.0", "remove-accents": "^0.5.0", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", @@ -68710,9 +68710,9 @@ }, "dependencies": { "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "uuid": { "version": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -69298,8 +69298,8 @@ "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } }, "@wordpress/env": { @@ -89715,9 +89715,9 @@ } }, "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "requires": { "loose-envify": "^1.1.0" } @@ -89797,12 +89797,12 @@ "dev": true }, "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "requires": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" } }, "react-element-to-jsx-string": { @@ -90232,20 +90232,20 @@ } }, "react-test-renderer": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", "dev": true, "requires": { - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "dependencies": { "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true } } @@ -91477,9 +91477,9 @@ } }, "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "requires": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index fc9b52821918d2..00abc92ea1cb3b 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "@storybook/source-loader": "7.6.15", "@storybook/theming": "7.6.15", "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "14.0.0", + "@testing-library/react": "14.3.0", "@testing-library/react-native": "12.4.3", "@testing-library/user-event": "14.4.3", "@types/eslint": "8.56.9", @@ -231,12 +231,12 @@ "postcss-loader": "6.2.1", "prettier": "npm:wp-prettier@3.0.3", "progress": "2.0.3", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "react-native": "0.73.3", "react-native-url-polyfill": "1.1.2", "react-refresh": "0.14.0", - "react-test-renderer": "18.2.0", + "react-test-renderer": "18.3.1", "reassure": "0.7.1", "redux": "4.1.2", "resize-observer-polyfill": "1.5.1", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 077d0284613e73..b0510a508ae47d 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -40,7 +40,7 @@ "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", - "react": "^18.2.0" + "react": "^18.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 9628dff1573666..be926f2a828594 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -50,7 +50,7 @@ "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "react-is": "^18.2.0", + "react-is": "^18.3.0", "remove-accents": "^0.5.0", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", diff --git a/packages/components/src/focal-point-picker/tooltip/index.native.js b/packages/components/src/focal-point-picker/tooltip/index.native.js index 626fb130c43dce..a8e9e74abd0045 100644 --- a/packages/components/src/focal-point-picker/tooltip/index.native.js +++ b/packages/components/src/focal-point-picker/tooltip/index.native.js @@ -54,7 +54,7 @@ function Tooltip( { children, onPress, style, visible } ) { ); } -function Label( { align, text, xOffset, yOffset } ) { +function Label( { align = 'center', text, xOffset = 0, yOffset = 0 } ) { const animationValue = useRef( new Animated.Value( 0 ) ).current; const [ dimensions, setDimensions ] = useState( null ); const visible = useContext( TooltipContext ); @@ -139,12 +139,6 @@ function Label( { align, text, xOffset, yOffset } ) { ); } -Label.defaultProps = { - align: 'center', - xOffset: 0, - yOffset: 0, -}; - Tooltip.Label = Label; export default Tooltip; diff --git a/packages/element/package.json b/packages/element/package.json index 04d9acfbdebb52..7958b71da7ad1d 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -34,8 +34,8 @@ "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" }, "publishConfig": { "access": "public" diff --git a/platform-docs/package.json b/platform-docs/package.json index 574345f30f393f..d2d40a9258a0b9 100644 --- a/platform-docs/package.json +++ b/platform-docs/package.json @@ -25,8 +25,8 @@ "@mdx-js/react": "^3.0.0", "clsx": "^1.2.1", "docusaurus-lunr-search": "^3.3.2", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", From 98562da732bcfb34f79f9412086503e74a53fdd8 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Tue, 30 Apr 2024 13:33:12 +0200 Subject: [PATCH 64/97] withBlockTree: simplify code that replaces/removes controlled blocks (#61234) Co-authored-by: jsnajdr Co-authored-by: ellatrix --- packages/block-editor/src/store/reducer.js | 36 ++++++++-------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 13024d4d2e8fac..3ea0fb46273049 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -304,20 +304,15 @@ const withBlockTree = action.blocks ); newState.tree = new Map( newState.tree ); - action.replacedClientIds - .concat( - // Controlled inner blocks are only removed - // if the block doesn't move to another position - // otherwise their content will be lost. - action.replacedClientIds - .filter( - ( clientId ) => ! inserterClientIds[ clientId ] - ) - .map( ( clientId ) => 'controlled||' + clientId ) - ) - .forEach( ( key ) => { - newState.tree.delete( key ); - } ); + action.replacedClientIds.forEach( ( clientId ) => { + newState.tree.delete( clientId ); + // Controlled inner blocks are only removed + // if the block doesn't move to another position + // otherwise their content will be lost. + if ( ! inserterClientIds[ clientId ] ) { + newState.tree.delete( 'controlled||' + clientId ); + } + } ); updateBlockTreeForBlocks( newState, action.blocks ); updateParentInnerBlocksInTree( @@ -358,15 +353,10 @@ const withBlockTree = } } newState.tree = new Map( newState.tree ); - action.removedClientIds - .concat( - action.removedClientIds.map( - ( clientId ) => 'controlled||' + clientId - ) - ) - .forEach( ( key ) => { - newState.tree.delete( key ); - } ); + action.removedClientIds.forEach( ( clientId ) => { + newState.tree.delete( clientId ); + newState.tree.delete( 'controlled||' + clientId ); + } ); updateParentInnerBlocksInTree( newState, parentsOfRemovedBlocks, From 848b466b259d4ebc781be01671325138ade6139f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 30 Apr 2024 14:08:48 +0200 Subject: [PATCH 65/97] ESLint Plugin: Handle multi-line translator comments (#61096) --- .../rules/__tests__/i18n-translator-comments.js | 17 +++++++++++++++++ .../rules/i18n-translator-comments.js | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/rules/__tests__/i18n-translator-comments.js b/packages/eslint-plugin/rules/__tests__/i18n-translator-comments.js index df59a9bbe07083..59098aa9eac056 100644 --- a/packages/eslint-plugin/rules/__tests__/i18n-translator-comments.js +++ b/packages/eslint-plugin/rules/__tests__/i18n-translator-comments.js @@ -34,6 +34,23 @@ sprintf( // translators: %s: Color i18n.sprintf( i18n.__( 'Color: %s' ), color );`, }, + { + code: ` +sprintf( + /* + * translators: %s is the name of the city we couldn't locate. + * Replace the examples with cities related to your locale. Test that + * they match the expected location and have upcoming events before + * including them. If no cities related to your locale have events, + * then use cities related to your locale that would be recognizable + * to most users. Use only the city name itself, without any region + * or country. Use the endonym (native locale name) instead of the + * English name if possible. + */ + __( 'We couldn’t locate %s. Please try another nearby city. For example: Kansas City; Springfield; Portland.' ), +templateParams.unknownCity +);`, + }, ], invalid: [ { diff --git a/packages/eslint-plugin/rules/i18n-translator-comments.js b/packages/eslint-plugin/rules/i18n-translator-comments.js index 86d57449dc0547..56d563d3fdb72c 100644 --- a/packages/eslint-plugin/rules/i18n-translator-comments.js +++ b/packages/eslint-plugin/rules/i18n-translator-comments.js @@ -78,7 +78,7 @@ module.exports = { const { value: commentText, loc: { - start: { line: commentLine }, + end: { line: commentLine }, }, } = comment; From 739506bf43915d1898aa5b2ed1e80b0204da5393 Mon Sep 17 00:00:00 2001 From: Rich Tabor Date: Tue, 30 Apr 2024 08:35:41 -0400 Subject: [PATCH 66/97] DocumentBar: Account for when top toolbar is open (#61118) * Add is-opened when top toolbar is opened * Try has-expanded-toolbar Co-authored-by: richtabor Co-authored-by: jeryj Co-authored-by: jameskoster --- .../src/components/header-edit-mode/index.js | 11 ++--------- .../components/header-edit-mode/style.scss | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 1bf925a928b6ed..55974a9cf9fe95 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -98,6 +98,7 @@ export default function HeaderEditMode() {
    { hasDefaultEditorCanvasView && ( @@ -120,15 +121,7 @@ export default function HeaderEditMode() { ) } { ! isDistractionFree && ( -
    +
    { ! hasDefaultEditorCanvasView ? ( getEditorCanvasContainerTitle( editorCanvasView ) ) : ( diff --git a/packages/edit-site/src/components/header-edit-mode/style.scss b/packages/edit-site/src/components/header-edit-mode/style.scss index 4095437a454e67..5963a1c8151410 100644 --- a/packages/edit-site/src/components/header-edit-mode/style.scss +++ b/packages/edit-site/src/components/header-edit-mode/style.scss @@ -10,6 +10,19 @@ border-bottom: $border-width solid $gray-200; padding-left: $header-height; + // When top toolbar is engaged and should expand fully. + &.show-block-toolbar { + + .edit-site-header-edit-mode__start, + .edit-site-header-edit-mode__end { + flex-basis: auto; + } + + .edit-site-header-edit-mode__center { + display: none; + } + } + .edit-site-header-edit-mode__start { display: flex; border: none; @@ -183,12 +196,6 @@ } } -.has-fixed-toolbar { - .edit-site-header-edit-mode__center.is-collapsed { - display: none; - } -} - .components-popover.more-menu-dropdown__content { z-index: z-index(".components-popover.more-menu__content"); } From 4595915c9d547bb2d0d2ff974b60f0edeb21904e Mon Sep 17 00:00:00 2001 From: Maggie Date: Tue, 30 Apr 2024 05:38:21 -0700 Subject: [PATCH 67/97] Fix zoom out mode background color on Safari (#60873) * moved zoomed out class to the iframe * revert changes, add an extra class to the iframe * moved css to block editor * changed background color of iframe instead of adding a new class * leave background in gray for all cases on the iframe * removed white background color extra styles * remove another background color that is no longer needed Co-authored-by: MaggieCabrera Co-authored-by: draganescu Co-authored-by: youknowriad Co-authored-by: ellatrix Co-authored-by: richtabor Co-authored-by: getdave Co-authored-by: jameskoster --- packages/block-editor/src/components/block-canvas/style.scss | 4 +--- packages/edit-site/src/components/block-editor/style.scss | 1 - packages/edit-site/src/style.scss | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 2dc9e32d7a393c..6ec97bfb38853c 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -2,11 +2,9 @@ iframe[name="editor-canvas"] { width: 100%; height: 100%; display: block; + background-color: $gray-300; } -iframe[name="editor-canvas"]:not(.has-editor-padding) { - background-color: $white; -} iframe[name="editor-canvas"].has-editor-padding { padding: $grid-unit-30 $grid-unit-30 0; diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss index fa7fd7e13df3e4..67dad91361246f 100644 --- a/packages/edit-site/src/components/block-editor/style.scss +++ b/packages/edit-site/src/components/block-editor/style.scss @@ -31,7 +31,6 @@ display: block; width: 100%; height: 100%; - background: $white; } .edit-site-visual-editor__editor-canvas { diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 8c0117a5233501..b95a10a951661e 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -93,9 +93,6 @@ body.js.site-editor-php { display: none; } - .interface-interface-skeleton__content { - background-color: $gray-300; - } } /** From 280d316235c81e912484492f1e9d78b0144a4d8a Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 30 Apr 2024 15:43:52 +0300 Subject: [PATCH 68/97] Add publish flow in site editor (#61136) Co-authored-by: ntsekouras Co-authored-by: Mamaduka Co-authored-by: youknowriad Co-authored-by: jameskoster Co-authored-by: draganescu Co-authored-by: jasmussen Co-authored-by: Marc-pi --- packages/base-styles/_z-index.scss | 1 + .../higher-order/navigate-regions/style.scss | 6 +- .../src/editor/site-editor.ts | 25 +- .../edit-post/src/components/header/index.js | 2 +- .../edit-post/src/components/layout/index.js | 5 +- .../src/components/layout/style.scss | 80 ------ .../src/components/preferences-modal/index.js | 24 +- .../edit-site/src/components/editor/index.js | 39 ++- .../src/components/header-edit-mode/index.js | 32 ++- .../page-panels/page-status.js | 249 ------------------ .../page-panels/page-summary.js | 19 +- .../sidebar-edit-mode/page-panels/style.scss | 34 --- packages/edit-site/src/style.scss | 1 - .../entities-saved-states/style.scss | 7 + .../post-publish-button-or-toggle.js | 7 +- .../test/post-publish-button-or-toggle.js} | 0 .../components/post-publish-panel/style.scss | 43 +++ .../enable-publish-sidebar.js | 8 +- .../src/components/preferences-modal/index.js | 14 + .../components/save-publish-panels/index.js} | 33 +-- .../components/save-publish-panels/style.scss | 36 +++ packages/editor/src/private-apis.js | 4 + packages/editor/src/style.scss | 1 + .../components/interface-skeleton/style.scss | 2 +- .../site-editor/multi-entity-saving.spec.js | 15 +- test/e2e/specs/site-editor/pages.spec.js | 4 +- test/e2e/specs/site-editor/patterns.spec.js | 2 +- .../specs/site-editor/template-revert.spec.js | 29 +- 28 files changed, 256 insertions(+), 466 deletions(-) delete mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js delete mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss rename packages/{edit-post/src/components/header => editor/src/components/post-publish-button}/post-publish-button-or-toggle.js (96%) rename packages/{edit-post/src/components/header/test/index.js => editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js} (100%) rename packages/{edit-post => editor}/src/components/preferences-modal/enable-publish-sidebar.js (71%) rename packages/{edit-post/src/components/layout/actions-panel.js => editor/src/components/save-publish-panels/index.js} (73%) create mode 100644 packages/editor/src/components/save-publish-panels/style.scss diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index d995ccb31bbf5f..1191c16670ba15 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -88,6 +88,7 @@ $z-layers: ( // #wpadminbar { z-index: 99999 } ".interface-interface-skeleton__sidebar": 100000, ".edit-post-layout__toggle-sidebar-panel": 100000, + ".editor-layout__toggle-sidebar-panel": 100000, ".edit-widgets-sidebar": 100000, ".edit-post-layout .edit-post-post-publish-panel": 100001, // For larger views, the wp-admin navbar dropdown should be at top of diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index f146413a09dc9e..b3a4a0c1a9d1b5 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -29,9 +29,9 @@ // Header top bar when Distraction free mode is on. &.is-distraction-free .interface-interface-skeleton__header .edit-post-header, - .interface-interface-skeleton__sidebar .edit-post-layout__toggle-sidebar-panel, - .interface-interface-skeleton__actions .edit-post-layout__toggle-publish-panel, - .interface-interface-skeleton__actions .edit-post-layout__toggle-entities-saved-states-panel, + .interface-interface-skeleton__sidebar .editor-layout__toggle-sidebar-panel, + .interface-interface-skeleton__actions .editor-layout__toggle-publish-panel, + .interface-interface-skeleton__actions .editor-layout__toggle-entities-saved-states-panel, .editor-post-publish-panel { outline: 4px solid $components-color-accent; outline-offset: -4px; diff --git a/packages/e2e-test-utils-playwright/src/editor/site-editor.ts b/packages/e2e-test-utils-playwright/src/editor/site-editor.ts index fa8bb69131ec95..02723a60ed0dba 100644 --- a/packages/e2e-test-utils-playwright/src/editor/site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/editor/site-editor.ts @@ -21,21 +21,34 @@ export async function saveSiteEditorEntities( const editorTopBar = this.page.getByRole( 'region', { name: 'Editor top bar', } ); - const savePanel = this.page.getByRole( 'region', { name: 'Save panel' } ); + // If we have changes in a single entity which can be published the label is `Publish`. + const saveButton = editorTopBar.getByRole( 'button', { + name: 'Save', + exact: true, + } ); + const publishButton = editorTopBar.getByRole( 'button', { + name: 'Publish', + } ); + const publishButtonIsVisible = ! ( await saveButton.isVisible() ); // First Save button in the top bar. - await editorTopBar - .getByRole( 'button', { name: 'Save', exact: true } ) - .click(); + const buttonToClick = publishButtonIsVisible ? publishButton : saveButton; + await buttonToClick.click(); if ( ! options.isOnlyCurrentEntityDirty ) { // Second Save button in the entities panel. - await savePanel + await this.page + .getByRole( 'region', { + name: /(Editor publish|Save panel)/, + } ) .getByRole( 'button', { name: 'Save', exact: true } ) .click(); } + // The text in the notice can be different based on the edited entity, whether + // we are saving multiple entities and whether we publish or update. So for now, + // we locate it based on the last part. await this.page .getByRole( 'button', { name: 'Dismiss this notice' } ) - .getByText( 'Site updated.' ) + .getByText( /(updated|published)\./ ) .waitFor(); } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 32f2cc1ccd276d..ff21478307754a 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -24,7 +24,6 @@ import { useState } from '@wordpress/element'; */ import FullscreenModeClose from './fullscreen-mode-close'; import PostEditorMoreMenu from './more-menu'; -import PostPublishButtonOrToggle from './post-publish-button-or-toggle'; import MainDashboardButton from './main-dashboard-button'; import { store as editPostStore } from '../../store'; import { unlock } from '../../lock-unlock'; @@ -36,6 +35,7 @@ const { PreviewDropdown, PinnedItems, MoreMenu, + PostPublishButtonOrToggle, } = unlock( editorPrivateApis ); const slideY = { diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index e814611744e83f..ba64fc7321190b 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -48,7 +48,6 @@ import Header from '../header'; import SettingsSidebar from '../sidebar/settings-sidebar'; import MetaBoxes from '../meta-boxes'; import WelcomeGuide from '../welcome-guide'; -import ActionsPanel from './actions-panel'; import { store as editPostStore } from '../../store'; import { unlock } from '../../lock-unlock'; import useCommonCommands from '../../hooks/commands/use-common-commands'; @@ -61,6 +60,7 @@ const { ListViewSidebar, ComplementaryArea, FullscreenMode, + SavePublishPanels, InterfaceSkeleton, interfaceStore, } = unlock( editorPrivateApis ); @@ -349,7 +349,7 @@ function Layout( { initialPost } ) { ) } actions={ - } shortcuts={ { diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index e226e9c65a3e08..b53b75bb5ec70b 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -26,83 +26,3 @@ } @include editor-left(".edit-post-layout .components-editor-notices__snackbar"); - -.edit-post-layout .editor-post-publish-panel { - position: fixed; - z-index: z-index(".edit-post-layout .edit-post-post-publish-panel"); - top: $admin-bar-height-big; - bottom: 0; - right: 0; - left: 0; - overflow: auto; - - @include break-medium() { - z-index: z-index(".edit-post-layout .edit-post-post-publish-panel {greater than small}"); - top: $admin-bar-height; - left: auto; - width: $sidebar-width + $border-width; - border-left: $border-width solid $gray-300; - transform: translateX(+100%); - animation: edit-post-post-publish-panel__slide-in-animation 0.1s forwards; - @include reduce-motion("animation"); - - body.is-fullscreen-mode & { - top: 0; - } - - // Keep it open on focus to avoid conflict with navigate-regions animation. - [role="region"]:focus & { - transform: translateX(0%); - } - } -} - -@keyframes edit-post-post-publish-panel__slide-in-animation { - 100% { - transform: translateX(0%); - } -} - -.edit-post-layout .editor-post-publish-panel__header-publish-button { - justify-content: center; -} - -.edit-post-layout__toggle-publish-panel, -.edit-post-layout__toggle-sidebar-panel, -.edit-post-layout__toggle-entities-saved-states-panel { - z-index: z-index(".edit-post-layout__toggle-sidebar-panel"); - position: fixed !important; // Need to override the default relative positionning - top: -9999em; - bottom: auto; - left: auto; - right: 0; - box-sizing: border-box; - width: $sidebar-width; - background-color: $white; - border: 1px dotted $gray-300; - height: auto !important; // Need to override the default sidebar positionnings - padding: $grid-unit-30; - display: flex; - justify-content: center; -} - -.edit-post-layout__toggle-sidebar-panel { - .interface-interface-skeleton__sidebar:focus &, - .interface-interface-skeleton__sidebar:focus-within & { - top: auto; - bottom: 0; - } -} - -.edit-post-layout__toggle-entities-saved-states-panel, -.edit-post-layout__toggle-publish-panel { - .interface-interface-skeleton__actions:focus &, - .interface-interface-skeleton__actions:focus-within & { - top: auto; - bottom: 0; - } -} - -.edit-post-layout .entities-saved-states__panel-header { - height: $header-height + $border-width; -} diff --git a/packages/edit-post/src/components/preferences-modal/index.js b/packages/edit-post/src/components/preferences-modal/index.js index d7950dc2a81263..9ed517cc76d283 100644 --- a/packages/edit-post/src/components/preferences-modal/index.js +++ b/packages/edit-post/src/components/preferences-modal/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; -import { useViewportMatch } from '@wordpress/compose'; import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; @@ -12,32 +11,13 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor'; */ import { unlock } from '../../lock-unlock'; import MetaBoxesSection from './meta-boxes-section'; -import EnablePublishSidebarOption from './enable-publish-sidebar'; -const { PreferencesModalSection, PreferenceToggleControl } = unlock( - preferencesPrivateApis -); +const { PreferenceToggleControl } = unlock( preferencesPrivateApis ); const { PreferencesModal } = unlock( editorPrivateApis ); export default function EditPostPreferencesModal() { - const isLargeViewport = useViewportMatch( 'medium' ); - const extraSections = { - general: ( - <> - { isLargeViewport && ( - - - - ) } - - - ), + general: , appearance: ( { + if ( typeof entitiesSavedStatesCallback === 'function' ) { + entitiesSavedStatesCallback( arg ); + } + setEntitiesSavedStatesCallback( false ); + }, + [ entitiesSavedStatesCallback ] + ); + const isReady = ! isLoading && ( ( postWithTemplate && !! contextPost && !! editedPost ) || @@ -223,6 +241,8 @@ export default function Editor( { isLoading, onClick } ) { 'edit-site-editor__interface-skeleton', { 'show-icon-labels': showIconLabels, + 'is-entity-save-view-open': + !! entitiesSavedStatesCallback, } ) } header={ @@ -249,11 +269,28 @@ export default function Editor( { isLoading, onClick } ) { ease: [ 0.6, 0, 0.4, 1 ], } } > -
    +
    ) } } + actions={ + + } notices={ } content={ <> diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 55974a9cf9fe95..486c6e0e1046c9 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -14,6 +14,7 @@ import { __unstableMotion as motion } from '@wordpress/components'; import { store as preferencesStore } from '@wordpress/preferences'; import { DocumentBar, + PostSavedState, store as editorStore, privateApis as editorPrivateApis, } from '@wordpress/editor'; @@ -31,6 +32,7 @@ import { } from '../editor-canvas-container'; import { unlock } from '../../lock-unlock'; import { FOCUSABLE_ENTITIES } from '../../utils/constants'; +import { isPreviewingTheme } from '../../utils/is-previewing-theme'; const { CollapsableBlockToolbar, @@ -38,9 +40,10 @@ const { PostViewLink, PreviewDropdown, PinnedItems, + PostPublishButtonOrToggle, } = unlock( editorPrivateApis ); -export default function HeaderEditMode() { +export default function HeaderEditMode( { setEntitiesSavedStatesCallback } ) { const { templateType, isDistractionFree, @@ -48,6 +51,7 @@ export default function HeaderEditMode() { showIconLabels, editorCanvasView, isFixedToolbar, + isPublishSidebarOpened, } = useSelect( ( select ) => { const { getEditedPostType } = select( editSiteStore ); const { __unstableGetEditorMode } = select( blockEditorStore ); @@ -64,6 +68,8 @@ export default function HeaderEditMode() { ).getEditorCanvasContainerView(), isDistractionFree: getPreference( 'core', 'distractionFree' ), isFixedToolbar: getPreference( 'core', 'fixedToolbar' ), + isPublishSidebarOpened: + select( editorStore ).isPublishSidebarOpened(), }; }, [] ); @@ -94,6 +100,7 @@ export default function HeaderEditMode() { ease: 'easeOut', }; + const _isPreviewingTheme = isPreviewingTheme(); return (
    ) } - + { + // TODO: For now we conditionally render the Save/Publish buttons based on + // some specific site editor extra handling. Examples are when we're previewing + // a theme, handling of global styles changes or when we're in 'view' mode, + // which opens the save panel in a Modal. + } + { ! _isPreviewingTheme && ! isPublishSidebarOpened && ( + // This button isn't completely hidden by the publish sidebar. + // We can't hide the whole toolbar when the publish sidebar is open because + // we want to prevent mounting/unmounting the PostPublishButtonOrToggle DOM node. + // We track that DOM node to return focus to the PostPublishButtonOrToggle + // when the publish sidebar has been closed. + + ) } + { ! _isPreviewingTheme && ( + + ) } + { _isPreviewingTheme && } { ! isDistractionFree && } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js deleted file mode 100644 index a1c60276c7994d..00000000000000 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js +++ /dev/null @@ -1,249 +0,0 @@ -/** - * WordPress dependencies - */ -import { - Button, - ToggleControl, - Dropdown, - __experimentalText as Text, - __experimentalVStack as VStack, - TextControl, - RadioControl, - VisuallyHidden, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useDispatch } from '@wordpress/data'; -import { useState, useMemo } from '@wordpress/element'; -import { store as coreStore } from '@wordpress/core-data'; -import { store as noticesStore } from '@wordpress/notices'; -import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; -import { useInstanceId } from '@wordpress/compose'; -import { privateApis as editorPrivateApis } from '@wordpress/editor'; - -/** - * Internal dependencies - */ -import StatusLabel from '../../sidebar-navigation-screen-page/status-label'; -import { unlock } from '../../../lock-unlock'; - -const { PostPanelRow } = unlock( editorPrivateApis ); - -const STATUS_OPTIONS = [ - { - label: ( - <> - { __( 'Draft' ) } - { __( 'Not ready to publish.' ) } - - ), - value: 'draft', - }, - { - label: ( - <> - { __( 'Pending' ) } - - { __( 'Waiting for review before publishing.' ) } - - - ), - value: 'pending', - }, - { - label: ( - <> - { __( 'Private' ) } - - { __( 'Only visible to site admins and editors.' ) } - - - ), - value: 'private', - }, - { - label: ( - <> - { __( 'Scheduled' ) } - - { __( 'Publish automatically on a chosen date.' ) } - - - ), - value: 'future', - }, - { - label: ( - <> - { __( 'Published' ) } - { __( 'Visible to everyone.' ) } - - ), - value: 'publish', - }, -]; - -export default function PageStatus( { - postType, - postId, - status, - password, - date, -} ) { - const [ showPassword, setShowPassword ] = useState( !! password ); - const instanceId = useInstanceId( PageStatus ); - - const { editEntityRecord } = useDispatch( coreStore ); - const { createErrorNotice } = useDispatch( noticesStore ); - - const [ popoverAnchor, setPopoverAnchor ] = useState( null ); - // Memoize popoverProps to avoid returning a new object every time. - const popoverProps = useMemo( - () => ( { - // Anchor the popover to the middle of the entire row so that it doesn't - // move around when the label changes. - anchor: popoverAnchor, - 'aria-label': __( 'Change status' ), - placement: 'bottom-end', - } ), - [ popoverAnchor ] - ); - - const saveStatus = async ( { - status: newStatus = status, - password: newPassword = password, - date: newDate = date, - } ) => { - try { - await editEntityRecord( 'postType', postType, postId, { - status: newStatus, - date: newDate, - password: newPassword, - } ); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while updating the status' ); - - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } - }; - - const handleTogglePassword = ( value ) => { - setShowPassword( value ); - if ( ! value ) { - saveStatus( { password: '' } ); - } - }; - - const handleStatus = ( value ) => { - let newDate = date; - let newPassword = password; - if ( value === 'publish' ) { - if ( new Date( date ) > new Date() ) { - newDate = null; - } - } else if ( value === 'future' ) { - if ( ! date || new Date( date ) < new Date() ) { - newDate = new Date(); - newDate.setDate( newDate.getDate() + 7 ); - } - } else if ( value === 'private' && password ) { - setShowPassword( false ); - newPassword = ''; - } - saveStatus( { - status: value, - date: newDate, - password: newPassword, - } ); - }; - - return ( - - ( - - ) } - renderContent={ ( { onClose } ) => ( - <> - -
    - - - { status !== 'private' && ( -
    - - { __( 'Password' ) } - - - { showPassword && ( -
    - - { __( 'Create password' ) } - - - saveStatus( { - password: value, - } ) - } - value={ password } - placeholder={ __( - 'Use a secure password' - ) } - type="text" - id={ `edit-site-change-status__password-input-${ instanceId }` } - /> -
    - ) } -
    - ) } -
    -
    - - ) } - /> -
    - ); -} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js index 5b8710ac3abe42..b0bee04d0f75c3 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js @@ -15,32 +15,19 @@ import { /** * Internal dependencies */ -import PageStatus from './page-status'; import { unlock } from '../../../lock-unlock'; -const { PrivatePostExcerptPanel } = unlock( editorPrivateApis ); +const { PrivatePostExcerptPanel, PostStatus } = unlock( editorPrivateApis ); -export default function PageSummary( { - status, - date, - password, - postId, - postType, -} ) { +export default function PageSummary() { return ( { ( fills ) => ( <> + - diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss deleted file mode 100644 index 426bf103bb41ab..00000000000000 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss +++ /dev/null @@ -1,34 +0,0 @@ -.edit-site-change-status__content { - .components-popover__content { - min-width: 320px; - padding: $grid-unit-20; - } - - .edit-site-change-status__options { - .components-base-control__field > .components-v-stack { - gap: $grid-unit-10; - } - - label { - .components-text { - display: block; - } - } - } - - .edit-site-change-status__password-legend { - padding: 0; - margin-bottom: $grid-unit-10; - } -} - -.edit-site-summary-field__trigger { - max-width: 100%; - - // Truncate - display: block; - text-align: left; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index b95a10a951661e..d0002e1070ce3e 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -13,7 +13,6 @@ @import "./components/page-templates/style.scss"; @import "./components/table/style.scss"; @import "./components/sidebar-edit-mode/style.scss"; -@import "./components/sidebar-edit-mode/page-panels/style.scss"; @import "./components/sidebar-edit-mode/settings-header/style.scss"; @import "./components/sidebar-edit-mode/template-panel/style.scss"; @import "./components/editor/style.scss"; diff --git a/packages/editor/src/components/entities-saved-states/style.scss b/packages/editor/src/components/entities-saved-states/style.scss index 981a0d92e5ff6b..f4e0c9f814c24f 100644 --- a/packages/editor/src/components/entities-saved-states/style.scss +++ b/packages/editor/src/components/entities-saved-states/style.scss @@ -30,3 +30,10 @@ margin-bottom: $grid-unit-05; } } + +.edit-post-layout, +.edit-site-editor__interface-skeleton { + .entities-saved-states__panel-header { + height: $header-height + $border-width; + } +} diff --git a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js b/packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js similarity index 96% rename from packages/edit-post/src/components/header/post-publish-button-or-toggle.js rename to packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js index 88670647767f14..bf742bef1429bb 100644 --- a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js +++ b/packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js @@ -3,7 +3,12 @@ */ import { useViewportMatch, compose } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; -import { PostPublishButton, store as editorStore } from '@wordpress/editor'; + +/** + * Internal dependencies + */ +import PostPublishButton from './index'; +import { store as editorStore } from '../../store'; export function PostPublishButtonOrToggle( { forceIsDirty, diff --git a/packages/edit-post/src/components/header/test/index.js b/packages/editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js similarity index 100% rename from packages/edit-post/src/components/header/test/index.js rename to packages/editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js diff --git a/packages/editor/src/components/post-publish-panel/style.scss b/packages/editor/src/components/post-publish-panel/style.scss index ceefe6a618d731..037980074ce0a9 100644 --- a/packages/editor/src/components/post-publish-panel/style.scss +++ b/packages/editor/src/components/post-publish-panel/style.scss @@ -188,3 +188,46 @@ } } } + +.edit-post-layout, +.edit-site-editor__interface-skeleton { + .editor-post-publish-panel { + position: fixed; + z-index: z-index(".edit-post-layout .edit-post-post-publish-panel"); + top: $admin-bar-height-big; + bottom: 0; + right: 0; + left: 0; + overflow: auto; + + @include break-medium() { + z-index: z-index(".edit-post-layout .edit-post-post-publish-panel {greater than small}"); + top: $admin-bar-height; + left: auto; + width: $sidebar-width + $border-width; + border-left: $border-width solid $gray-300; + transform: translateX(+100%); + animation: editor-post-publish-panel__slide-in-animation 0.1s forwards; + @include reduce-motion("animation"); + + body.is-fullscreen-mode & { + top: 0; + } + + // Keep it open on focus to avoid conflict with navigate-regions animation. + [role="region"]:focus & { + transform: translateX(0%); + } + } + } + + .editor-post-publish-panel__header-publish-button { + justify-content: center; + } +} + +@keyframes editor-post-publish-panel__slide-in-animation { + 100% { + transform: translateX(0%); + } +} diff --git a/packages/edit-post/src/components/preferences-modal/enable-publish-sidebar.js b/packages/editor/src/components/preferences-modal/enable-publish-sidebar.js similarity index 71% rename from packages/edit-post/src/components/preferences-modal/enable-publish-sidebar.js rename to packages/editor/src/components/preferences-modal/enable-publish-sidebar.js index 84f4e72a946091..bcdfb2540e8dac 100644 --- a/packages/edit-post/src/components/preferences-modal/enable-publish-sidebar.js +++ b/packages/editor/src/components/preferences-modal/enable-publish-sidebar.js @@ -3,14 +3,13 @@ */ import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; -import { ifViewportMatches } from '@wordpress/viewport'; -import { store as editorStore } from '@wordpress/editor'; import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { store as editorStore } from '../../store'; const { PreferenceBaseOption } = unlock( preferencesPrivateApis ); @@ -25,8 +24,5 @@ export default compose( onChange: ( isEnabled ) => isEnabled ? enablePublishSidebar() : disablePublishSidebar(), }; - } ), - // In < medium viewports we override this option and always show the publish sidebar. - // See the edit-post's header component for the specific logic. - ifViewportMatches( 'medium' ) + } ) )( PreferenceBaseOption ); diff --git a/packages/editor/src/components/preferences-modal/index.js b/packages/editor/src/components/preferences-modal/index.js index 77afe434cca02d..46e0f9e9a5a561 100644 --- a/packages/editor/src/components/preferences-modal/index.js +++ b/packages/editor/src/components/preferences-modal/index.js @@ -17,6 +17,7 @@ import { store as interfaceStore } from '@wordpress/interface'; */ import EnablePanelOption from './enable-panel'; import EnablePluginDocumentSettingPanelOption from './enable-plugin-document-setting-panel'; +import EnablePublishSidebarOption from './enable-publish-sidebar'; import BlockManager from '../block-manager'; import PostTaxonomies from '../post-taxonomies'; import PostFeaturedImageCheck from '../post-featured-image/check'; @@ -136,6 +137,18 @@ export default function EditorPreferencesModal( { extraSections = {} } ) { /> + { isLargeViewport && ( + + + + ) } { extraSections?.general } ), @@ -258,6 +271,7 @@ export default function EditorPreferencesModal( { extraSections = {} } ) { setIsInserterOpened, setIsListViewOpened, setPreference, + isLargeViewport, ] ); diff --git a/packages/edit-post/src/components/layout/actions-panel.js b/packages/editor/src/components/save-publish-panels/index.js similarity index 73% rename from packages/edit-post/src/components/layout/actions-panel.js rename to packages/editor/src/components/save-publish-panels/index.js index 6914cf6ff3c068..812b1dcc1df41f 100644 --- a/packages/edit-post/src/components/layout/actions-panel.js +++ b/packages/editor/src/components/save-publish-panels/index.js @@ -1,13 +1,6 @@ /** * WordPress dependencies */ -import { - EntitiesSavedStates, - PostPublishPanel, - PluginPrePublishPanel, - PluginPostPublishPanel, - store as editorStore, -} from '@wordpress/editor'; import { useSelect, useDispatch } from '@wordpress/data'; import { Button, createSlotFill } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -16,28 +9,28 @@ import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import { store as editPostStore } from '../../store'; +import EntitiesSavedStates from '../entities-saved-states'; +import PostPublishPanel from '../post-publish-panel'; +import PluginPrePublishPanel from '../plugin-pre-publish-panel'; +import PluginPostPublishPanel from '../plugin-post-publish-panel'; +import { store as editorStore } from '../../store'; const { Fill, Slot } = createSlotFill( 'ActionsPanel' ); export const ActionsPanelFill = Fill; -export default function ActionsPanel( { +export default function SavePublishPanels( { setEntitiesSavedStatesCallback, closeEntitiesSavedStates, isEntitiesSavedStatesOpen, + forceIsDirtyPublishPanel, } ) { const { closePublishSidebar, togglePublishSidebar } = useDispatch( editorStore ); - const { - publishSidebarOpened, - hasActiveMetaboxes, - hasNonPostEntityChanges, - } = useSelect( + const { publishSidebarOpened, hasNonPostEntityChanges } = useSelect( ( select ) => ( { publishSidebarOpened: select( editorStore ).isPublishSidebarOpened(), - hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), hasNonPostEntityChanges: select( editorStore ).hasNonPostEntityChanges(), } ), @@ -56,17 +49,17 @@ export default function ActionsPanel( { unmountableContent = ( ); } else if ( hasNonPostEntityChanges ) { unmountableContent = ( -
    +