From 73f6649d33738832180a9b722d590f0fdfbb24c4 Mon Sep 17 00:00:00 2001 From: Rich Tabor Date: Mon, 31 Jul 2023 15:25:20 -0400 Subject: [PATCH 001/150] Remove extraneous "Detach" ToolbarButton for synced patterns (#53121) * Removes the ToolbarButton * Update e2e tests --------- Co-authored-by: George Mamadashvili --- packages/block-library/src/block/edit.js | 40 +------------------ .../editor/various/reusable-blocks.test.js | 9 +++-- 2 files changed, 7 insertions(+), 42 deletions(-) diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 0b1f3b01b75b9d..13745ae0fd6dec 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { useDispatch, useSelect } from '@wordpress/data'; import { useEntityBlockEditor, useEntityProp, @@ -10,8 +9,6 @@ import { import { Placeholder, Spinner, - ToolbarGroup, - ToolbarButton, TextControl, PanelBody, } from '@wordpress/components'; @@ -21,16 +18,12 @@ import { __experimentalRecursionProvider as RecursionProvider, __experimentalUseHasRecursion as useHasRecursion, InnerBlocks, - BlockControls, InspectorControls, useBlockProps, Warning, - store as blockEditorStore, } from '@wordpress/block-editor'; -import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; -import { ungroup } from '@wordpress/icons'; -export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { +export default function ReusableBlockEdit( { attributes: { ref } } ) { const hasAlreadyRendered = useHasRecursion( ref ); const { record, hasResolved } = useEntityRecord( 'postType', @@ -39,21 +32,6 @@ export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { ); const isMissing = hasResolved && ! record; - const { canRemove, innerBlockCount } = useSelect( - ( select ) => { - const { canRemoveBlock, getBlockCount } = - select( blockEditorStore ); - return { - canRemove: canRemoveBlock( clientId ), - innerBlockCount: getBlockCount( clientId ), - }; - }, - [ clientId ] - ); - - const { __experimentalConvertBlockToStatic: convertBlockToStatic } = - useDispatch( reusableBlocksStore ); - const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', 'wp_block', @@ -112,22 +90,6 @@ export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { return ( - { canRemove && ( - - - convertBlockToStatic( clientId ) } - label={ - innerBlockCount > 1 - ? __( 'Detach patterns' ) - : __( 'Detach pattern' ) - } - icon={ ungroup } - showTooltip - /> - - - ) } { await insertReusableBlock( 'Surprised greeting block' ); // Convert block to a regular block. - await clickBlockToolbarButton( 'Detach pattern' ); + await clickBlockToolbarButton( 'Options' ); + await clickMenuItem( 'Detach pattern' ); // Check that we have a paragraph block on the page. const paragraphBlock = await canvas().$( @@ -217,7 +218,8 @@ describe( 'Reusable blocks', () => { await insertReusableBlock( 'Multi-selection reusable block' ); // Convert block to a regular block. - await clickBlockToolbarButton( 'Detach patterns' ); + await clickBlockToolbarButton( 'Options' ); + await clickMenuItem( 'Detach patterns' ); // Check that we have two paragraph blocks on the page. expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -349,7 +351,8 @@ describe( 'Reusable blocks', () => { // Convert back to regular blocks. await clickBlockToolbarButton( 'Select Edited block' ); - await clickBlockToolbarButton( 'Detach pattern' ); + await clickBlockToolbarButton( 'Options' ); + await clickMenuItem( 'Detach pattern' ); await page.waitForXPath( selector, { hidden: true, } ); From 654a8a2bb5f5e978a76da41e10c35fcedd37bc4c Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 1 Aug 2023 04:47:10 +0900 Subject: [PATCH 002/150] Image block: fix image size at wide and full width (#53184) --- packages/block-library/src/image/image.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index fd410fe3cbe5f2..5fe5c93f4199b4 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -566,9 +566,9 @@ export default function Image( { className={ borderProps.className } style={ { width: - ( width && height ) || aspectRatio ? '100%' : 'inherit', + ( width && height ) || aspectRatio ? '100%' : undefined, height: - ( width && height ) || aspectRatio ? '100%' : 'inherit', + ( width && height ) || aspectRatio ? '100%' : undefined, objectFit: scale, ...borderProps.style, } } From f69f489b48aeacbe9da0df1519f8d2970c0ce21a Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Mon, 31 Jul 2023 14:28:28 -0700 Subject: [PATCH 003/150] Settings: Show message when Visual or Code editor are disabled (#52737) * Settings: Show message when Visual or Code editor are disabled * Use disabled menu items instead of a `` * Softer language Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> * Better description Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> * Import `disabled` prop * Mention `disabled` in `menu-item/README.md` * Add a changelog entry * Remove superfluous message --------- Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> --- packages/components/CHANGELOG.md | 1 + packages/components/src/menu-item/README.md | 7 +++++ .../src/menu-items-choice/index.tsx | 1 + .../components/src/menu-items-choice/types.ts | 5 ++++ .../components/header/mode-switcher/index.js | 27 ++++++++++++++++--- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c903989f456288..2cfc0c1f5caeba 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements - `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)). +- `MenuItemsChoice`, `MenuItem`: Support a `disabled` prop on a menu item ([#52737](https://github.com/WordPress/gutenberg/pull/52737)). ### Bug Fix diff --git a/packages/components/src/menu-item/README.md b/packages/components/src/menu-item/README.md index 68affcd63b8bfd..8532e54d4132da 100644 --- a/packages/components/src/menu-item/README.md +++ b/packages/components/src/menu-item/README.md @@ -34,6 +34,13 @@ MenuItem supports the following props. Any additional props are passed through t Element to render as child of button. +### `disabled` + +- Type: `boolean` +- Required: No + +Refer to documentation for [Button's `disabled` prop](/packages/components/src/button/README.md#disabled-boolean). + ### `info` - Type: `string` diff --git a/packages/components/src/menu-items-choice/index.tsx b/packages/components/src/menu-items-choice/index.tsx index 171e5fae72a60f..f909dceda22e9d 100644 --- a/packages/components/src/menu-items-choice/index.tsx +++ b/packages/components/src/menu-items-choice/index.tsx @@ -58,6 +58,7 @@ function MenuItemsChoice( { { - if ( choice.value !== mode ) { + if ( ! isCodeEditingEnabled && choice.value === 'text' ) { + choice = { + ...choice, + disabled: true, + }; + } + if ( ! isRichEditingEnabled && choice.value === 'visual' ) { + choice = { + ...choice, + disabled: true, + info: __( + 'You can enable the visual editor in your profile settings.' + ), + }; + } + if ( choice.value !== selectedMode && ! choice.disabled ) { return { ...choice, shortcut }; } return choice; @@ -70,7 +89,7 @@ function ModeSwitcher() { From e27e91a85b4487d12030cb8c6b0fada0896bb97f Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 31 Jul 2023 16:19:41 -0700 Subject: [PATCH 004/150] Enforce Labels Workflow: Treat "Backports from WordPress Core" as allowable label. None of the existing "[Type]" labels describe a backport, though one describes code that requires backporting into Core. In this change the backport label satisfies the labeling requirement. Alternatively a new "[Type] Backport from WordPress Core" label could be created, but that would imply requiring two practically identical labels for each backport PR. --- .github/workflows/enforce-pr-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/enforce-pr-labels.yml b/.github/workflows/enforce-pr-labels.yml index 1909bf66da216f..c6e198489259d6 100644 --- a/.github/workflows/enforce-pr-labels.yml +++ b/.github/workflows/enforce-pr-labels.yml @@ -12,7 +12,7 @@ jobs: with: mode: exactly count: 1 - labels: '[Type] Accessibility (a11y), [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket' + labels: '[Type] Accessibility (a11y), [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core' add_comment: true message: "## ⚠️ Type of PR label error\n To merge this PR, it requires {{ errorString }} {{ count }} label indicating the type of PR. Other labels are optional and not being checked here. \n- **Type-related labels to choose from**: {{ provided }}.\n- **Labels found**: {{ applied }}.\n\nYou can learn more about the Type labels in Gutenberg [here](https://github.com/WordPress/gutenberg/labels?q=type)" exit_type: failure From d5912c66260d1102a83630b597523ba3ed1ee3a6 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 1 Aug 2023 10:57:23 +1000 Subject: [PATCH 005/150] Render the footer conditionally in the global styles sidebar component so that any side effects from the footer wrapper are not rendered, e.g., styles and what not (#53204) Ensure that the precise bottom margin persists if the footer isn't rendered --- .../index.js | 80 ++++++++++--------- .../sidebar-navigation-screen/index.js | 12 ++- .../sidebar-navigation-screen/style.scss | 4 + 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js index c885ca97ba7893..ce689c095d7eb5 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js @@ -108,30 +108,10 @@ function SidebarNavigationScreenGlobalStylesContent() { ); } -function SidebarNavigationScreenGlobalStylesFooter( { onClickRevisions } ) { - const { revisions, isLoading } = useGlobalStylesRevisions(); - const { revisionsCount } = useSelect( ( select ) => { - const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = - select( coreStore ); - - const globalStylesId = __experimentalGetCurrentGlobalStylesId(); - const globalStyles = globalStylesId - ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) - : undefined; - - return { - revisionsCount: - globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0, - }; - }, [] ); - - const hasRevisions = revisionsCount >= 2; - const modified = revisions?.[ 0 ]?.modified; - - if ( ! hasRevisions || isLoading || ! modified ) { - return null; - } - +function SidebarNavigationScreenGlobalStylesFooter( { + modifiedDateTime, + onClickRevisions, +} ) { return ( - @@ -162,6 +142,8 @@ function SidebarNavigationScreenGlobalStylesFooter( { onClickRevisions } ) { } export default function SidebarNavigationScreenGlobalStyles() { + const { revisions, isLoading: isLoadingRevisions } = + useGlobalStylesRevisions(); const { openGeneralSidebar, setIsListViewOpened } = useDispatch( editSiteStore ); const isMobileViewport = useViewportMatch( 'medium', '<' ); @@ -171,15 +153,28 @@ export default function SidebarNavigationScreenGlobalStyles() { const { createNotice } = useDispatch( noticesStore ); const { set: setPreference } = useDispatch( preferencesStore ); const { get: getPreference } = useSelect( preferencesStore ); - const { isViewMode, isStyleBookOpened } = useSelect( ( select ) => { - const { getCanvasMode, getEditorCanvasContainerView } = unlock( - select( editSiteStore ) - ); - return { - isViewMode: 'view' === getCanvasMode(), - isStyleBookOpened: 'style-book' === getEditorCanvasContainerView(), - }; - }, [] ); + const { isViewMode, isStyleBookOpened, revisionsCount } = useSelect( + ( select ) => { + const { getCanvasMode, getEditorCanvasContainerView } = unlock( + select( editSiteStore ) + ); + const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = + select( coreStore ); + const globalStylesId = __experimentalGetCurrentGlobalStylesId(); + const globalStyles = globalStylesId + ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) + : undefined; + return { + isViewMode: 'view' === getCanvasMode(), + isStyleBookOpened: + 'style-book' === getEditorCanvasContainerView(), + revisionsCount: + globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? + 0, + }; + }, + [] + ); const turnOffDistractionFreeMode = useCallback( () => { const isDistractionFree = getPreference( @@ -226,6 +221,12 @@ export default function SidebarNavigationScreenGlobalStyles() { setEditorCanvasContainerView( 'global-styles-revisions' ); }, [ openGlobalStyles, setEditorCanvasContainerView ] ); + // If there are no revisions, do not render a footer. + const hasRevisions = revisionsCount >= 2; + const modifiedDateTime = revisions?.[ 0 ]?.modified; + const shouldShowGlobalStylesFooter = + hasRevisions && ! isLoadingRevisions && modifiedDateTime; + return ( <> } footer={ - + shouldShowGlobalStylesFooter && ( + + ) } actions={ <> diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/index.js b/packages/edit-site/src/components/sidebar-navigation-screen/index.js index d26cc4c7d62a09..f8824332f51528 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -51,7 +56,12 @@ export default function SidebarNavigationScreen( { return ( <> diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen/style.scss index 26a30da286fc84..65fe323157b9a1 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen/style.scss @@ -9,6 +9,10 @@ // Ensure the sidebar is always at least as tall as the viewport. // This allows the footer section to be sticky at the bottom of the viewport. flex-grow: 1; + margin-bottom: $grid-unit-20; + &.has-footer { + margin-bottom: 0; + } } .edit-site-sidebar-navigation-screen__content { From da0f6ab75e8d2dbec5d6eab3b661df569049f462 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 1 Aug 2023 03:18:29 +0200 Subject: [PATCH 006/150] Fix regression with Edit site Navigate regions (#52940) * Make the navigabel region wrap the inert sidebar. * Adjust animation. --- .../edit-site/src/components/layout/index.js | 47 +++++++++++-------- .../src/components/layout/style.scss | 4 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 1a1c2f3a3830a2..75a42360d599d1 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -260,28 +260,35 @@ export default function Layout() {
- - + - - + + diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 2fc8bbc6a5091e..11c7bdeeaf2a19 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -58,7 +58,7 @@ display: flex; } -.edit-site-layout__sidebar { +.edit-site-layout__sidebar-region { z-index: z-index(".edit-site-layout__sidebar"); width: 100vw; flex-shrink: 0; @@ -75,7 +75,7 @@ top: 0; } - > div { + .edit-site-layout__sidebar { display: flex; flex-direction: column; height: 100%; From ee967291e6584b827949dd9377ce6815a7e6ab63 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 1 Aug 2023 10:12:14 +0800 Subject: [PATCH 007/150] Fix not expanding pattern in page editor (#53169) --------- Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> --- packages/block-library/src/pattern/edit.js | 20 ++++++++++++++++++- .../disable-non-page-content-blocks.js | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/pattern/edit.js b/packages/block-library/src/pattern/edit.js index b4900536ec274f..824960f90b3798 100644 --- a/packages/block-library/src/pattern/edit.js +++ b/packages/block-library/src/pattern/edit.js @@ -9,7 +9,12 @@ import { useBlockProps, } from '@wordpress/block-editor'; -const PatternEdit = ( { attributes, clientId } ) => { +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; + +const PatternEdit = ( { attributes, clientId, rootClientId } ) => { const selectedPattern = useSelect( ( select ) => select( blockEditorStore ).__experimentalGetParsedPattern( @@ -20,6 +25,8 @@ const PatternEdit = ( { attributes, clientId } ) => { const { replaceBlocks, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); + const { setBlockEditingMode } = unlock( useDispatch( blockEditorStore ) ); + const { getBlockEditingMode } = unlock( useSelect( blockEditorStore ) ); // Run this effect when the component loads. // This adds the Pattern's contents to the post. @@ -38,15 +45,26 @@ const PatternEdit = ( { attributes, clientId } ) => { const clonedBlocks = selectedPattern.blocks.map( ( block ) => cloneBlock( block ) ); + const rootEditingMode = getBlockEditingMode( rootClientId ); + // Temporarily set the root block to default mode to allow replacing the pattern. + // This could happen when the page is disabling edits of non-content blocks. + __unstableMarkNextChangeAsNotPersistent(); + setBlockEditingMode( rootClientId, 'default' ); __unstableMarkNextChangeAsNotPersistent(); replaceBlocks( clientId, clonedBlocks ); + // Restore the root block's original mode. + __unstableMarkNextChangeAsNotPersistent(); + setBlockEditingMode( rootClientId, rootEditingMode ); } ); } }, [ + rootClientId, clientId, selectedPattern?.blocks, __unstableMarkNextChangeAsNotPersistent, replaceBlocks, + getBlockEditingMode, + setBlockEditingMode, ] ); const props = useBlockProps(); diff --git a/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js b/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js index 7b184ff253c7e6..eb0c622840a756 100644 --- a/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js +++ b/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js @@ -49,7 +49,7 @@ export function useDisableNonPageContentBlocks() { const withDisableNonPageContentBlocks = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const isDescendentOfQueryLoop = !! props.context.queryId; + const isDescendentOfQueryLoop = props.context.queryId !== undefined; const isPageContent = PAGE_CONTENT_BLOCK_TYPES.includes( props.name ) && ! isDescendentOfQueryLoop; From cddff11e9b21aeee8ac3e13362b70e3e8e571b84 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Tue, 1 Aug 2023 04:27:06 +0200 Subject: [PATCH 008/150] Footnotes: fix published preview (#53072) * Footnotes: fix published preview * remove var dump * Fix php lint * PHP lint * Address feedback * Add e2e test --- .../block-library/src/footnotes/index.php | 73 +++++++++++++++++++ packages/core-data/src/actions.js | 9 ++- packages/editor/src/store/selectors.js | 4 +- .../specs/editor/various/footnotes.spec.js | 40 ++++++++++ 4 files changed, 121 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php index 9815033820406b..712cd571c8b0e9 100644 --- a/packages/block-library/src/footnotes/index.php +++ b/packages/block-library/src/footnotes/index.php @@ -212,3 +212,76 @@ function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) { return get_metadata( 'post', $revision->ID, $field, true ); } add_filter( 'wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 ); + +/** + * The REST API autosave endpoint doesn't save meta, so we can use the + * `wp_creating_autosave` when it updates an exiting autosave, and + * `_wp_put_post_revision` when it creates a new autosave. + * + * @since 6.3.0 + * + * @param int|array $autosave The autosave ID or array. + */ +function _wp_rest_api_autosave_meta( $autosave ) { + // Ensure it's a REST API request. + if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { + return; + } + + $body = rest_get_server()->get_raw_data(); + $body = json_decode( $body, true ); + + if ( ! isset( $body['meta']['footnotes'] ) ) { + return; + } + + // `wp_creating_autosave` passes the array, + // `_wp_put_post_revision` passes the ID. + $id = is_int( $autosave ) ? $autosave : $autosave['ID']; + + if ( ! $id ) { + return; + } + + update_post_meta( $id, 'footnotes', $body['meta']['footnotes'] ); +} +// See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L391C1-L391C1. +add_action( 'wp_creating_autosave', '_wp_rest_api_autosave_meta' ); +// See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L398. +// Then https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/revision.php#L367. +add_action( '_wp_put_post_revision', '_wp_rest_api_autosave_meta' ); + +/** + * This is a workaround for the autosave endpoint returning early if the + * revision field are equal. The problem is that "footnotes" is not real + * revision post field, so there's nothing to compare against. + * + * This trick sets the "footnotes" field (value doesn't matter), which will + * cause the autosave endpoint to always update the latest revision. That should + * be fine, it should be ok to update the revision even if nothing changed. Of + * course, this is temporary fix. + * + * @since 6.3.0 + * + * @param WP_Post $prepared_post The prepared post object. + * @param WP_REST_Request $request The request object. + * + * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L365-L384. + * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L219. + */ +function _wp_rest_api_force_autosave_difference( $prepared_post, $request ) { + // We only want to be altering POST requests. + if ( $request->get_method() !== 'POST' ) { + return $prepared_post; + } + + // Only alter requests for the '/autosaves' route. + if ( substr( $request->get_route(), -strlen( '/autosaves' ) ) !== '/autosaves' ) { + return $prepared_post; + } + + $prepared_post->footnotes = '[]'; + return $prepared_post; +} + +add_filter( 'rest_pre_insert_post', '_wp_rest_api_force_autosave_difference', 10, 2 ); diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 824e1159286ede..bfe4a4a185f3f4 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -551,9 +551,12 @@ export const saveEntityRecord = data = Object.keys( data ).reduce( ( acc, key ) => { if ( - [ 'title', 'excerpt', 'content' ].includes( - key - ) + [ + 'title', + 'excerpt', + 'content', + 'meta', + ].includes( key ) ) { acc[ key ] = data[ key ]; } diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index d8be5d8e00af60..ae62492e79042d 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -611,8 +611,8 @@ export const isEditedPostAutosaveable = createRegistrySelector( return true; } - // If the title or excerpt has changed, the post is autosaveable. - return [ 'title', 'excerpt' ].some( + // If title, excerpt, or meta have changed, the post is autosaveable. + return [ 'title', 'excerpt', 'meta' ].some( ( field ) => getPostRawValue( autosave[ field ] ) !== getEditedPostAttribute( state, field ) diff --git a/test/e2e/specs/editor/various/footnotes.spec.js b/test/e2e/specs/editor/various/footnotes.spec.js index ad3ab149095d2e..b96d4530cb4990 100644 --- a/test/e2e/specs/editor/various/footnotes.spec.js +++ b/test/e2e/specs/editor/various/footnotes.spec.js @@ -360,4 +360,44 @@ test.describe( 'Footnotes', () => { }, ] ); } ); + + test( 'can be previewed when published', async ( { editor, page } ) => { + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( 'a' ); + + await editor.showBlockToolbar(); + await editor.clickBlockToolbarButton( 'More' ); + await page.locator( 'button:text("Footnote")' ).click(); + + await page.keyboard.type( '1' ); + + // Publish post. + await editor.publishPost(); + + await editor.canvas.click( 'ol.wp-block-footnotes li span' ); + await page.keyboard.press( 'End' ); + await page.keyboard.type( '2' ); + + const editorPage = page; + const previewPage = await editor.openPreviewPage(); + + await expect( + previewPage.locator( 'ol.wp-block-footnotes li' ) + ).toHaveText( '12 ↩︎' ); + + await previewPage.close(); + await editorPage.bringToFront(); + + // Test again, this time with an existing revision (different code + // path). + await editor.canvas.click( 'ol.wp-block-footnotes li span' ); + await page.keyboard.press( 'End' ); + await page.keyboard.type( '3' ); + + const previewPage2 = await editor.openPreviewPage(); + + await expect( + previewPage2.locator( 'ol.wp-block-footnotes li' ) + ).toHaveText( '123 ↩︎' ); + } ); } ); From fbc3712c8d5bab35b7d2d0f85878b4830350dc37 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 1 Aug 2023 12:29:34 +1000 Subject: [PATCH 009/150] Footnotes: disable for synced patterns and prevent duplication for pages in site editor (#53003) * Initial commit: - Prevent footnote creation withing core/block - Only insert a footnote if one isn't found in the entity block list * Try grabbing controlled blocks from parent post content block * Cache `selectedClientId` Get hasParentCoreBlocks using separate useSelect call. * Rename hasParentCoreBlocks to isBlockWithinPattern Add comments * Removing while loop since we're already fetching the post content parent in the `getBlockParentsByBlockName` call above * Reinstating while loop because it can deal with nested blocks --------- Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> --- .../block-library/src/footnotes/format.js | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/footnotes/format.js b/packages/block-library/src/footnotes/format.js index 2086005a509931..c7730c53316b77 100644 --- a/packages/block-library/src/footnotes/format.js +++ b/packages/block-library/src/footnotes/format.js @@ -26,6 +26,10 @@ import { unlock } from '../lock-unlock'; const { usesContextKey } = unlock( privateApis ); export const formatName = 'core/footnote'; + +const POST_CONTENT_BLOCK_NAME = 'core/post-content'; +const SYNCED_PATTERN_BLOCK_NAME = 'core/block'; + export const format = { title: __( 'Footnote' ), tagName: 'sup', @@ -44,13 +48,30 @@ export const format = { const registry = useRegistry(); const { getSelectedBlockClientId, + getBlocks, getBlockRootClientId, getBlockName, - getBlocks, + getBlockParentsByBlockName, } = useSelect( blockEditorStore ); const footnotesBlockType = useSelect( ( select ) => select( blocksStore ).getBlockType( name ) ); + /* + * This useSelect exists because we need to use its return value + * outside the event callback. + */ + const isBlockWithinPattern = useSelect( ( select ) => { + const { + getBlockParentsByBlockName: _getBlockParentsByBlockName, + getSelectedBlockClientId: _getSelectedBlockClientId, + } = select( blockEditorStore ); + const parentCoreBlocks = _getBlockParentsByBlockName( + _getSelectedBlockClientId(), + SYNCED_PATTERN_BLOCK_NAME + ); + return parentCoreBlocks && parentCoreBlocks.length > 0; + }, [] ); + const { selectionChange, insertBlock } = useDispatch( blockEditorStore ); @@ -62,6 +83,11 @@ export const format = { return null; } + // Checks if the selected block lives within a pattern. + if ( isBlockWithinPattern ) { + return null; + } + function onClick() { registry.batch( () => { let id; @@ -86,10 +112,27 @@ export const format = { onChange( newValue ); } + const selectedClientId = getSelectedBlockClientId(); + + /* + * Attempts to find a common parent post content block. + * This allows for locating blocks within a page edited in the site editor. + */ + const parentPostContent = getBlockParentsByBlockName( + selectedClientId, + POST_CONTENT_BLOCK_NAME + ); + + // When called with a post content block, getBlocks will return + // the block with controlled inner blocks included. + const blocks = parentPostContent.length + ? getBlocks( parentPostContent[ 0 ] ) + : getBlocks(); + // BFS search to find the first footnote block. let fnBlock = null; { - const queue = [ ...getBlocks() ]; + const queue = [ ...blocks ]; while ( queue.length ) { const block = queue.shift(); if ( block.name === name ) { @@ -104,12 +147,11 @@ export const format = { // When there is no footnotes block in the post, create one and // insert it at the bottom. if ( ! fnBlock ) { - const clientId = getSelectedBlockClientId(); - let rootClientId = getBlockRootClientId( clientId ); + let rootClientId = getBlockRootClientId( selectedClientId ); while ( rootClientId && - getBlockName( rootClientId ) !== 'core/post-content' + getBlockName( rootClientId ) !== POST_CONTENT_BLOCK_NAME ) { rootClientId = getBlockRootClientId( rootClientId ); } From 561d2447ea5e8292ad1b05fe64cd02c374d9ef79 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Tue, 1 Aug 2023 05:17:06 +0200 Subject: [PATCH 010/150] Footnotes: add missing _ in revision field filter (#53135) * Footnotes: add missing _ in revision field filter * Use correct hook name * Revert prefixing callback names --- packages/block-library/src/footnotes/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php index 712cd571c8b0e9..bd7734d7d02d15 100644 --- a/packages/block-library/src/footnotes/index.php +++ b/packages/block-library/src/footnotes/index.php @@ -211,7 +211,7 @@ function wp_add_footnotes_to_revision( $fields ) { function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) { return get_metadata( 'post', $revision->ID, $field, true ); } -add_filter( 'wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 ); +add_filter( '_wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 ); /** * The REST API autosave endpoint doesn't save meta, so we can use the From 5efce0ecec77fec764c30ce53f6368554ce9eb1a Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Tue, 1 Aug 2023 07:07:53 +0300 Subject: [PATCH 011/150] don't display BlockContextualToolbar at all in contentonly locking (#53110) --- .../block-tools/block-contextual-toolbar.js | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index f0fc28c7fbbd21..7dcd88d1cfd10f 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -32,48 +32,56 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { const toolbarButtonRef = useRef(); const isLargeViewport = useViewportMatch( 'medium' ); - const { blockType, hasParents, showParentSelector, selectedBlockClientId } = - useSelect( ( select ) => { - const { - getBlockName, - getBlockParents, - getSelectedBlockClientIds, - getBlockEditingMode, - } = unlock( select( blockEditorStore ) ); - const { getBlockType } = select( blocksStore ); - const selectedBlockClientIds = getSelectedBlockClientIds(); - const _selectedBlockClientId = selectedBlockClientIds[ 0 ]; - const parents = getBlockParents( _selectedBlockClientId ); - const firstParentClientId = parents[ parents.length - 1 ]; - const parentBlockName = getBlockName( firstParentClientId ); - const parentBlockType = getBlockType( parentBlockName ); + const { + blockType, + hasParents, + showParentSelector, + selectedBlockClientId, + isContentOnly, + } = useSelect( ( select ) => { + const { + getBlockName, + getBlockParents, + getSelectedBlockClientIds, + getBlockEditingMode, + } = unlock( select( blockEditorStore ) ); + const { getBlockType } = select( blocksStore ); + const selectedBlockClientIds = getSelectedBlockClientIds(); + const _selectedBlockClientId = selectedBlockClientIds[ 0 ]; + const parents = getBlockParents( _selectedBlockClientId ); + const firstParentClientId = parents[ parents.length - 1 ]; + const parentBlockName = getBlockName( firstParentClientId ); + const parentBlockType = getBlockType( parentBlockName ); - return { - selectedBlockClientId: _selectedBlockClientId, - blockType: - _selectedBlockClientId && - getBlockType( getBlockName( _selectedBlockClientId ) ), - hasParents: parents.length, - showParentSelector: - parentBlockType && - getBlockEditingMode( firstParentClientId ) === 'default' && - hasBlockSupport( - parentBlockType, - '__experimentalParentSelector', - true - ) && - selectedBlockClientIds.length <= 1 && - getBlockEditingMode( _selectedBlockClientId ) === 'default', - }; - }, [] ); + return { + selectedBlockClientId: _selectedBlockClientId, + blockType: + _selectedBlockClientId && + getBlockType( getBlockName( _selectedBlockClientId ) ), + hasParents: parents.length, + isContentOnly: + getBlockEditingMode( _selectedBlockClientId ) === 'contentOnly', + showParentSelector: + parentBlockType && + getBlockEditingMode( firstParentClientId ) === 'default' && + hasBlockSupport( + parentBlockType, + '__experimentalParentSelector', + true + ) && + selectedBlockClientIds.length <= 1 && + getBlockEditingMode( _selectedBlockClientId ) === 'default', + }; + }, [] ); useEffect( () => { setIsCollapsed( false ); }, [ selectedBlockClientId ] ); if ( - blockType && - ! hasBlockSupport( blockType, '__experimentalToolbar', true ) + isContentOnly || + ( blockType && + ! hasBlockSupport( blockType, '__experimentalToolbar', true ) ) ) { return null; } From 09d26f87a83ccac0be4ff856d95c0b88379f2a7e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 1 Aug 2023 15:18:32 +1000 Subject: [PATCH 012/150] Pattern: Add getBlockRootClientId call (#53206) --- packages/block-library/src/pattern/edit.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/pattern/edit.js b/packages/block-library/src/pattern/edit.js index 824960f90b3798..bf5b84eeb02963 100644 --- a/packages/block-library/src/pattern/edit.js +++ b/packages/block-library/src/pattern/edit.js @@ -14,7 +14,7 @@ import { */ import { unlock } from '../lock-unlock'; -const PatternEdit = ( { attributes, clientId, rootClientId } ) => { +const PatternEdit = ( { attributes, clientId } ) => { const selectedPattern = useSelect( ( select ) => select( blockEditorStore ).__experimentalGetParsedPattern( @@ -26,7 +26,9 @@ const PatternEdit = ( { attributes, clientId, rootClientId } ) => { const { replaceBlocks, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); const { setBlockEditingMode } = unlock( useDispatch( blockEditorStore ) ); - const { getBlockEditingMode } = unlock( useSelect( blockEditorStore ) ); + const { getBlockRootClientId, getBlockEditingMode } = unlock( + useSelect( blockEditorStore ) + ); // Run this effect when the component loads. // This adds the Pattern's contents to the post. @@ -40,6 +42,7 @@ const PatternEdit = ( { attributes, clientId, rootClientId } ) => { // because nested pattern blocks cannot be inserted if the parent block supports // inner blocks but doesn't have blockSettings in the state. window.queueMicrotask( () => { + const rootClientId = getBlockRootClientId( clientId ); // Clone blocks from the pattern before insertion to ensure they receive // distinct client ids. See https://github.com/WordPress/gutenberg/issues/50628. const clonedBlocks = selectedPattern.blocks.map( ( block ) => @@ -58,13 +61,13 @@ const PatternEdit = ( { attributes, clientId, rootClientId } ) => { } ); } }, [ - rootClientId, clientId, selectedPattern?.blocks, __unstableMarkNextChangeAsNotPersistent, replaceBlocks, getBlockEditingMode, setBlockEditingMode, + getBlockRootClientId, ] ); const props = useBlockProps(); From 4ea0b5f4b59b234e114da8f290a10e36863c51fb Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 1 Aug 2023 17:38:01 +1200 Subject: [PATCH 013/150] Fix color and behavior of unsynced patterns in block inserter (#53205) --- .../components/inserter-list-item/index.js | 4 +++- .../inserter/hooks/use-block-types-state.js | 21 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index 43510cdd52d96e..dc755e87676233 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -49,7 +49,9 @@ function InserterListItem( { ]; }, [ item.name, item.initialAttributes, item.initialAttributes ] ); - const isSynced = isReusableBlock( item ) || isTemplatePart( item ); + const isSynced = + ( isReusableBlock( item ) && item.syncStatus !== 'unsynced' ) || + isTemplatePart( item ); return ( { ); const onSelectItem = useCallback( - ( { name, initialAttributes, innerBlocks }, shouldFocusBlock ) => { - const insertedBlock = createBlock( - name, - initialAttributes, - createBlocksFromInnerBlocksTemplate( innerBlocks ) - ); + ( + { name, initialAttributes, innerBlocks, syncStatus, content }, + shouldFocusBlock + ) => { + const insertedBlock = + syncStatus === 'unsynced' + ? parse( content, { + __unstableSkipMigrationLogs: true, + } ) + : createBlock( + name, + initialAttributes, + createBlocksFromInnerBlocksTemplate( innerBlocks ) + ); onInsert( insertedBlock, undefined, shouldFocusBlock ); }, From 0f8bb001450cbfbdab7adacee8721eacfe561b30 Mon Sep 17 00:00:00 2001 From: James Koster Date: Tue, 1 Aug 2023 09:19:45 +0100 Subject: [PATCH 014/150] Template Parts: Add further details to template part panel (#52476) --------- Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> --- .../style.scss | 1 + .../use-pattern-details.js | 110 +++++++++++------- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss index 3beef739f78082..2757ce5a620c58 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss @@ -18,6 +18,7 @@ .edit-site-sidebar-navigation-details-screen-panel__label.edit-site-sidebar-navigation-details-screen-panel__label { color: $gray-600; width: 100px; + flex-shrink: 0; } .edit-site-sidebar-navigation-details-screen-panel__value.edit-site-sidebar-navigation-details-screen-panel__value { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-pattern-details.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-pattern-details.js index 93d249355abe7e..5aed89955ba4fc 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-pattern-details.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-pattern-details.js @@ -1,10 +1,15 @@ +/** + * External dependencies + */ +import { sentenceCase } from 'change-case'; + /** * WordPress dependencies */ -import { __, sprintf, _x } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; +import { store as editorStore } from '@wordpress/editor'; import { useSelect } from '@wordpress/data'; -import { Icon } from '@wordpress/components'; /** * Internal dependencies @@ -28,6 +33,11 @@ export default function usePatternDetails( postType, postId ) { postType, postId ); + const templatePartAreas = useSelect( + ( select ) => + select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + [] + ); const currentTheme = useSelect( ( select ) => select( coreStore ).getCurrentTheme(), [] @@ -36,56 +46,24 @@ export default function usePatternDetails( postType, postId ) { const isAddedByActiveTheme = addedBy.type === 'theme' && record.theme === currentTheme?.stylesheet; const title = getTitle(); - let descriptionText = getDescription(); + let description = getDescription(); - if ( ! descriptionText && addedBy.text ) { - descriptionText = sprintf( + if ( ! description && addedBy.text ) { + description = sprintf( // translators: %s: pattern title e.g: "Header". __( 'This is the %s pattern.' ), getTitle() ); } - if ( ! descriptionText && postType === 'wp_block' && record?.title ) { - descriptionText = sprintf( + if ( ! description && postType === 'wp_block' && record?.title ) { + description = sprintf( // translators: %s: user created pattern title e.g. "Footer". __( 'This is the %s pattern.' ), record.title ); } - const description = ( - <> - { descriptionText } - - { addedBy.text && ! isAddedByActiveTheme && ( - - - - { addedBy.imageUrl ? ( - - ) : ( - - ) } - - { addedBy.text } - - - { addedBy.isCustomized && ( - - { _x( '(Customized)', 'pattern' ) } - - ) } - - ) } - - ); - const footer = !! record?.modified ? ( area.area === record.area + ); + + let areaDetailValue = templatePartArea?.label; + + if ( ! areaDetailValue ) { + areaDetailValue = record.area + ? sprintf( + // translators: %s: Sentenced cased template part area e.g: "My custom area". + __( '%s (removed)' ), + sentenceCase( record.area ) + ) + : __( 'None' ); + } + + details.push( { label: __( 'Area' ), value: areaDetailValue } ); + } + + if ( + postType === 'wp_template_part' && + addedBy.text && + ! isAddedByActiveTheme + ) { + details.push( { + label: __( 'Added by' ), + value: ( + + { addedBy.text } + + ), + } ); + } + + if ( + postType === 'wp_template_part' && + addedBy.text && + ( record.origin === 'plugin' || record.has_theme_file === true ) + ) { + details.push( { + label: __( 'Customized' ), + value: ( + + { addedBy.isCustomized ? __( 'Yes' ) : __( 'No' ) } + + ), + } ); + } + const content = ( <> + { useNavigationMenuContent( postType, postId ) } { !! details.length && ( ) } - { useNavigationMenuContent( postType, postId ) } ); From 1d9352f49fdaf58a9c6f32ee38571c30c6e21fb8 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Tue, 1 Aug 2023 10:27:53 +0200 Subject: [PATCH 015/150] [RNMobile] Add e2e util `addBlockUsingAppender` (#52736) * Add e2e util `addBlockUsingAppender` Fix query for Android * Add group block test data * Update group block test data --- .../__device-tests__/helpers/test-data.js | 30 +++++++++++++++++++ .../__device-tests__/pages/editor-page.js | 23 ++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index 66619e803bd5ed..c356d9a91d16a1 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -202,3 +202,33 @@ exports.galleryBlock = ` `; + +exports.groupNestedStructure = ` +
+

Level 1

+ + + + + + + +
+

Level 2

+ + + + + + + +
+

Level 3

+ + + + +
+
+
+`; diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index 6182066636b2ff..3fce46906fe3b8 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -554,6 +554,29 @@ class EditorPage { } while ( navigateUpElements.length > 0 ); } + // Adds a block by tapping on the appender button of blocks with inner blocks (e.g. Group block) + async addBlockUsingAppender( block, blockName ) { + const appenderButton = isAndroid() + ? await this.waitForElementToBeDisplayedByXPath( + `//android.widget.Button[@resource-id="appender-button"]` + ) + : await this.waitForElementToBeDisplayedById( 'appender-button' ); + await appenderButton.click(); + + // Click on block of choice. + const blockButton = await this.findBlockButton( blockName ); + + if ( isAndroid() ) { + await blockButton.click(); + } else { + await this.driver.execute( 'mobile: tap', { + element: blockButton, + x: 10, + y: 10, + } ); + } + } + // ========================= // Inline toolbar functions // ========================= From 9a5f3db5a304398a331b980f96982a36deb97818 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:23:06 +0300 Subject: [PATCH 016/150] Editor: Use hooks instead of HoCs for `PostPingbacks` (#53228) * Editor: Use hooks instead of HoCs * Move default inside --- .../src/components/post-pingbacks/index.js | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/editor/src/components/post-pingbacks/index.js b/packages/editor/src/components/post-pingbacks/index.js index b2274075f1fe4c..d6e1c419ee6f9d 100644 --- a/packages/editor/src/components/post-pingbacks/index.js +++ b/packages/editor/src/components/post-pingbacks/index.js @@ -3,17 +3,23 @@ */ import { __ } from '@wordpress/i18n'; import { CheckboxControl } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -function PostPingbacks( { pingStatus = 'open', ...props } ) { +function PostPingbacks() { + const pingStatus = useSelect( + ( select ) => + select( editorStore ).getEditedPostAttribute( 'ping_status' ) ?? + 'open', + [] + ); + const { editPost } = useDispatch( editorStore ); const onTogglePingback = () => - props.editPost( { + editPost( { ping_status: pingStatus === 'open' ? 'closed' : 'open', } ); @@ -27,14 +33,4 @@ function PostPingbacks( { pingStatus = 'open', ...props } ) { ); } -export default compose( [ - withSelect( ( select ) => { - return { - pingStatus: - select( editorStore ).getEditedPostAttribute( 'ping_status' ), - }; - } ), - withDispatch( ( dispatch ) => ( { - editPost: dispatch( editorStore ).editPost, - } ) ), -] )( PostPingbacks ); +export default PostPingbacks; From d5d9161f510d89dac0fae1fb8c63eee6441b3b07 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:46:03 +0300 Subject: [PATCH 017/150] Editor: Use hooks instead of HoCs in PostExcerpt (#53229) --- .../src/components/post-excerpt/index.js | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/editor/src/components/post-excerpt/index.js b/packages/editor/src/components/post-excerpt/index.js index 29e5bc0aee0890..47a1d3bf585850 100644 --- a/packages/editor/src/components/post-excerpt/index.js +++ b/packages/editor/src/components/post-excerpt/index.js @@ -3,22 +3,27 @@ */ import { __ } from '@wordpress/i18n'; import { ExternalLink, TextareaControl } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -function PostExcerpt( { excerpt, onUpdateExcerpt } ) { +function PostExcerpt() { + const excerpt = useSelect( + ( select ) => select( editorStore ).getEditedPostAttribute( 'excerpt' ), + [] + ); + const { editPost } = useDispatch( editorStore ); + return (
onUpdateExcerpt( value ) } + onChange={ ( value ) => editPost( { excerpt: value } ) } value={ excerpt } /> { - return { - excerpt: select( editorStore ).getEditedPostAttribute( 'excerpt' ), - }; - } ), - withDispatch( ( dispatch ) => ( { - onUpdateExcerpt( excerpt ) { - dispatch( editorStore ).editPost( { excerpt } ); - }, - } ) ), -] )( PostExcerpt ); +export default PostExcerpt; From c05c66c221ef3f5b43f094f229755adf3ef4515b Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:46:26 +0300 Subject: [PATCH 018/150] Editor: Use hooks instead of HoCs in PostComments (#53231) --- .../src/components/post-comments/index.js | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/editor/src/components/post-comments/index.js b/packages/editor/src/components/post-comments/index.js index ea774964c9dd19..85d9948575d057 100644 --- a/packages/editor/src/components/post-comments/index.js +++ b/packages/editor/src/components/post-comments/index.js @@ -3,17 +3,23 @@ */ import { __ } from '@wordpress/i18n'; import { CheckboxControl } from '@wordpress/components'; -import { compose } from '@wordpress/compose'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -function PostComments( { commentStatus = 'open', ...props } ) { +function PostComments() { + const commentStatus = useSelect( + ( select ) => + select( editorStore ).getEditedPostAttribute( 'comment_status' ) ?? + 'open', + [] + ); + const { editPost } = useDispatch( editorStore ); const onToggleComments = () => - props.editPost( { + editPost( { comment_status: commentStatus === 'open' ? 'closed' : 'open', } ); @@ -27,16 +33,4 @@ function PostComments( { commentStatus = 'open', ...props } ) { ); } -export default compose( [ - withSelect( ( select ) => { - return { - commentStatus: - select( editorStore ).getEditedPostAttribute( - 'comment_status' - ), - }; - } ), - withDispatch( ( dispatch ) => ( { - editPost: dispatch( editorStore ).editPost, - } ) ), -] )( PostComments ); +export default PostComments; From d3c6ce8bddcfae3a9dae68c8b31b2b85fbd79c29 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:14:22 +0300 Subject: [PATCH 019/150] Edit Post: Use hooks instead of HoCs in DiscussionPanel (#53232) --- .../sidebar/discussion-panel/index.js | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/discussion-panel/index.js b/packages/edit-post/src/components/sidebar/discussion-panel/index.js index f6f416ddebd8ce..c8e63f23fac8dd 100644 --- a/packages/edit-post/src/components/sidebar/discussion-panel/index.js +++ b/packages/edit-post/src/components/sidebar/discussion-panel/index.js @@ -8,8 +8,7 @@ import { PostPingbacks, PostTypeSupportCheck, } from '@wordpress/editor'; -import { compose } from '@wordpress/compose'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -21,7 +20,18 @@ import { store as editPostStore } from '../../../store'; */ const PANEL_NAME = 'discussion-panel'; -function DiscussionPanel( { isEnabled, isOpened, onTogglePanel } ) { +function DiscussionPanel() { + const { isEnabled, isOpened } = useSelect( ( select ) => { + const { isEditorPanelEnabled, isEditorPanelOpened } = + select( editPostStore ); + return { + isEnabled: isEditorPanelEnabled( PANEL_NAME ), + isOpened: isEditorPanelOpened( PANEL_NAME ), + }; + }, [] ); + + const { toggleEditorPanelOpened } = useDispatch( editPostStore ); + if ( ! isEnabled ) { return null; } @@ -31,7 +41,7 @@ function DiscussionPanel( { isEnabled, isOpened, onTogglePanel } ) { toggleEditorPanelOpened( PANEL_NAME ) } > @@ -49,19 +59,4 @@ function DiscussionPanel( { isEnabled, isOpened, onTogglePanel } ) { ); } -export default compose( [ - withSelect( ( select ) => { - return { - isEnabled: - select( editPostStore ).isEditorPanelEnabled( PANEL_NAME ), - isOpened: select( editPostStore ).isEditorPanelOpened( PANEL_NAME ), - }; - } ), - withDispatch( ( dispatch ) => ( { - onTogglePanel() { - return dispatch( editPostStore ).toggleEditorPanelOpened( - PANEL_NAME - ); - }, - } ) ), -] )( DiscussionPanel ); +export default DiscussionPanel; From ee3c7591aa5c9e351559b9f648b2fb363bae46c1 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 1 Aug 2023 11:55:33 -0400 Subject: [PATCH 020/150] test: Enable jest-console for native tests (#53125) * test: Enable jest-console for native tests Ensure accidental errors, warnings, or logs are not outputted during tests. * test: Assert Cover block accidental warning * test: Assert Embed block log * test: Assert Paragraph block accidental warning * test: Assert Buttons block accidental warning * test: Assert Heading block accidental warning * test: Assert text-color accidental warning * test: Address act warning in Editor test Toggling HTML mode results in expected re-renders. We must wrap the invocation in `act` to communicate that to Jest. * test: Replace unnecessary asynchronous query Synchronous queries are generally easier to comprehend. * test: Assert block-invalid-warning warning and error * test: Assert Draggable accidental warning * test: Assert setup-local logs * test: Remove redundant jest-console imports This library is now included universally for all tests via test environment configuration and setup files. * refactor: Remove translation log The log introduces additional, un-filterable noise that can impair one's ability to use logs for debugging. --- .../components/block-draggable/test/index.native.js | 1 - .../block-list/test/block-invalid-warning.native.js | 9 ++++++++- packages/block-library/src/buttons/test/edit.native.js | 4 ++++ packages/block-library/src/cover/test/edit.native.js | 8 ++++++++ packages/block-library/src/embed/test/index.native.js | 8 ++++++++ .../block-library/src/heading/test/index.native.js | 4 ++++ packages/block-library/src/image/test/edit.native.js | 1 - .../block-library/src/paragraph/test/edit.native.js | 8 ++++++++ packages/components/src/draggable/test/index.native.js | 4 ++++ packages/edit-post/src/test/editor.native.js | 7 ++++--- .../format-library/src/text-color/test/index.native.js | 4 ++++ packages/react-native-editor/src/setup-locale.js | 10 ---------- packages/react-native-editor/src/test/index.test.js | 1 - packages/rich-text/src/test/index.native.js | 1 - test/native/setup-after-env.js | 5 +++++ 15 files changed, 57 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/block-draggable/test/index.native.js b/packages/block-editor/src/components/block-draggable/test/index.native.js index 7484a543e29601..7f3234b2ba2239 100644 --- a/packages/block-editor/src/components/block-draggable/test/index.native.js +++ b/packages/block-editor/src/components/block-draggable/test/index.native.js @@ -16,7 +16,6 @@ import TextInputState from 'react-native/Libraries/Components/TextInput/TextInpu */ import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; import { registerCoreBlocks } from '@wordpress/block-library'; -import '@wordpress/jest-console'; /** * Internal dependencies diff --git a/packages/block-editor/src/components/block-list/test/block-invalid-warning.native.js b/packages/block-editor/src/components/block-list/test/block-invalid-warning.native.js index ccc6aae75a2669..f75a63723b0a6f 100644 --- a/packages/block-editor/src/components/block-list/test/block-invalid-warning.native.js +++ b/packages/block-editor/src/components/block-list/test/block-invalid-warning.native.js @@ -19,6 +19,10 @@ describe( 'Block invalid warning', () => { `, } ); + expect( console ).toHaveErrored(); + expect( console ).toHaveWarnedWith( + 'Encountered unexpected attribute `styless`.' + ); // Assert const warningElement = screen.getByText( /Problem displaying block./ ); @@ -32,7 +36,10 @@ describe( 'Block invalid warning', () => { `, } ); - + expect( console ).toHaveErrored(); + expect( console ).toHaveWarnedWith( + 'Encountered unexpected attribute `styless`.' + ); // Act fireEvent.press( screen.getByText( /Problem displaying block./ ) ); const spacerBlock = getBlock( screen, 'Spacer' ); diff --git a/packages/block-library/src/buttons/test/edit.native.js b/packages/block-library/src/buttons/test/edit.native.js index c8e1415b7d1f7a..7d8d994d9af890 100644 --- a/packages/block-library/src/buttons/test/edit.native.js +++ b/packages/block-library/src/buttons/test/edit.native.js @@ -324,6 +324,10 @@ describe( 'Buttons block', () => { // Tap one color fireEvent.press( screen.getByLabelText( 'Pale pink' ) ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\nColor > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); // Dismiss the Block Settings modal. fireEvent( blockSettingsModal, 'backdropPress' ); diff --git a/packages/block-library/src/cover/test/edit.native.js b/packages/block-library/src/cover/test/edit.native.js index 40f7620fd2ad23..0e61207bc0da3d 100644 --- a/packages/block-library/src/cover/test/edit.native.js +++ b/packages/block-library/src/cover/test/edit.native.js @@ -220,6 +220,10 @@ describe( 'when an image is attached', () => { '52' ); fireEvent.press( screen.getByLabelText( 'Apply' ) ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\nFocalPoint > params.onFocalPointChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { @@ -377,6 +381,10 @@ describe( 'color settings', () => { // Find the selected color. const colorPaletteButton = await screen.findByTestId( COLOR_PINK ); expect( colorPaletteButton ).toBeDefined(); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\nColor > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); // Select another color. const newColorButton = await screen.findByTestId( COLOR_RED ); diff --git a/packages/block-library/src/embed/test/index.native.js b/packages/block-library/src/embed/test/index.native.js index 9a3bad5e6a7340..0fade6f51a9edb 100644 --- a/packages/block-library/src/embed/test/index.native.js +++ b/packages/block-library/src/embed/test/index.native.js @@ -898,6 +898,10 @@ describe( 'Embed block', () => { // Select create embed option. fireEvent.press( editor.getByText( 'Create embed' ) ); + expect( console ).toHaveLoggedWith( + 'Processed HTML piece:\n\n', + `

${ expectedURL }

` + ); // Get the created embed block. const [ embedBlock ] = await editor.findAllByLabelText( @@ -942,6 +946,10 @@ describe( 'Embed block', () => { // Select create link option. fireEvent.press( editor.getByText( 'Create link' ) ); + expect( console ).toHaveLoggedWith( + 'Processed HTML piece:\n\n', + `

${ expectedURL }

` + ); // Get the link text. const linkText = await editor.findByDisplayValue( diff --git a/packages/block-library/src/heading/test/index.native.js b/packages/block-library/src/heading/test/index.native.js index 493ef7f84a79d6..5b7abbc91ad94a 100644 --- a/packages/block-library/src/heading/test/index.native.js +++ b/packages/block-library/src/heading/test/index.native.js @@ -71,6 +71,10 @@ describe( 'Heading block', () => { // Tap one color fireEvent.press( screen.getByLabelText( 'Pale pink' ) ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\nColor > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); // Dismiss the Block Settings modal. fireEvent( blockSettingsModal, 'backdropPress' ); diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 7d614e664c7fec..7a0fd15c854470 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -26,7 +26,6 @@ import { select, dispatch } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; import { store as coreStore } from '@wordpress/core-data'; import apiFetch from '@wordpress/api-fetch'; -import '@wordpress/jest-console'; /** * Internal dependencies diff --git a/packages/block-library/src/paragraph/test/edit.native.js b/packages/block-library/src/paragraph/test/edit.native.js index e1f73119d5393e..8220ad0888c795 100644 --- a/packages/block-library/src/paragraph/test/edit.native.js +++ b/packages/block-library/src/paragraph/test/edit.native.js @@ -414,6 +414,10 @@ describe( 'Paragraph block', () => { // Tap one color fireEvent.press( screen.getByLabelText( 'Pale pink' ) ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\nColor > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); // Dismiss the Block Settings modal. fireEvent( blockSettingsModal, 'backdropPress' ); @@ -669,6 +673,10 @@ describe( 'Paragraph block', () => { ); fireEvent.press( screen.getByLabelText( 'Text color' ) ); fireEvent.press( await screen.findByLabelText( 'Tertiary' ) ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\ntext-color > Palette > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); // Assert expect( getEditorHtml() ).toMatchInlineSnapshot( ` diff --git a/packages/components/src/draggable/test/index.native.js b/packages/components/src/draggable/test/index.native.js index d0553d4c944a5f..3a160d81a81fdd 100644 --- a/packages/components/src/draggable/test/index.native.js +++ b/packages/components/src/draggable/test/index.native.js @@ -110,6 +110,10 @@ describe( 'Draggable', () => { }, { state: State.END }, ] ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + '[Reanimated] You can not use setGestureState in non-worklet function.' + ); expect( onDragStart ).toHaveBeenCalledTimes( 1 ); expect( onDragStart ).toHaveBeenCalledWith( { diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index ebc6cc8b5f8825..4911fb5128655c 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -74,11 +74,12 @@ describe( 'Editor', () => { // Act const paragraphBlock = getBlock( screen, 'Paragraph' ); fireEvent.press( paragraphBlock ); - - toggleMode(); + act( () => { + toggleMode(); + } ); // Assert - const htmlEditor = await screen.findByLabelText( 'html-view-content' ); + const htmlEditor = screen.getByLabelText( 'html-view-content' ); expect( htmlEditor ).toBeVisible(); } ); } ); diff --git a/packages/format-library/src/text-color/test/index.native.js b/packages/format-library/src/text-color/test/index.native.js index 4b08bf16baf2bb..c7350cfe4bb6c0 100644 --- a/packages/format-library/src/text-color/test/index.native.js +++ b/packages/format-library/src/text-color/test/index.native.js @@ -83,6 +83,10 @@ describe( 'Text color', () => { const pinkColorButton = await screen.findByA11yHint( COLOR_PINK ); expect( pinkColorButton ).toBeDefined(); fireEvent.press( pinkColorButton ); + // TODO(jest-console): Fix the warning and remove the expect below. + expect( console ).toHaveWarnedWith( + `Non-serializable values were found in the navigation state. Check:\n\ntext-color > Palette > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.` + ); expect( getEditorHtml() ).toMatchSnapshot(); } ); diff --git a/packages/react-native-editor/src/setup-locale.js b/packages/react-native-editor/src/setup-locale.js index bb50c5e76d3fb9..a583f2e1e53192 100644 --- a/packages/react-native-editor/src/setup-locale.js +++ b/packages/react-native-editor/src/setup-locale.js @@ -35,16 +35,6 @@ export default ( ...extraTranslations, }; - if ( domain === 'default' ) { - // eslint-disable-next-line no-console - console.log( 'locale', locale, allTranslations ); - } else { - // Extra translations are already logged along with the default domain, so - // for other domains we can limit the output to their translations. - // eslint-disable-next-line no-console - console.log( `${ domain } - locale`, locale, translations ); - } - // Only change the locale if it's supported by gutenberg if ( translations || extraTranslations ) { setLocaleData( allTranslations, domain ); diff --git a/packages/react-native-editor/src/test/index.test.js b/packages/react-native-editor/src/test/index.test.js index 9aa163bc1a2f99..418a3e8b55c752 100644 --- a/packages/react-native-editor/src/test/index.test.js +++ b/packages/react-native-editor/src/test/index.test.js @@ -11,7 +11,6 @@ import * as wpHooks from '@wordpress/hooks'; import { registerCoreBlocks } from '@wordpress/block-library'; // eslint-disable-next-line no-restricted-imports import * as wpEditPost from '@wordpress/edit-post'; -import '@wordpress/jest-console'; /** * Internal dependencies diff --git a/packages/rich-text/src/test/index.native.js b/packages/rich-text/src/test/index.native.js index 3f963fba32dc06..e0ce7ff78d6ccf 100644 --- a/packages/rich-text/src/test/index.native.js +++ b/packages/rich-text/src/test/index.native.js @@ -15,7 +15,6 @@ import { setDefaultBlockName, unregisterBlockType, } from '@wordpress/blocks'; -import '@wordpress/jest-console'; /** * Internal dependencies diff --git a/test/native/setup-after-env.js b/test/native/setup-after-env.js index 5e71eb5dbd861f..bddd547faf5b80 100644 --- a/test/native/setup-after-env.js +++ b/test/native/setup-after-env.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import '@wordpress/jest-console'; + /** * Internal dependencies */ From 162e47789a5b6f855e926675e59145ce691bb1d2 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 1 Aug 2023 11:56:28 -0400 Subject: [PATCH 021/150] test: Reduce default Jest log verbosity (#53129) * test: Remove default Jest log verbosity The current approach of enabling the `verbose` option for all test scripts is counter to the Jest default and the Gutenberg web editor preset. This better aligns the native tests with the web tests. The `verbose` option remains enabled for "debug" scripts and can be enabled ad-hoc via the `--verbose` flag. * test: Remove default verbosity in native e2e tests --- package.json | 6 +++--- packages/react-native-editor/jest_ui.config.js | 1 - packages/react-native-editor/package.json | 6 +++--- test/native/jest.config.js | 1 - 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 192df39c60a726..468a80fd904d41 100644 --- a/package.json +++ b/package.json @@ -311,11 +311,11 @@ "test:e2e:playwright": "playwright test --config test/e2e/playwright.config.ts", "test:e2e:storybook": "playwright test --config test/storybook-playwright/playwright.config.ts", "test:e2e:watch": "npm run test:e2e -- --watch", - "test:native": "cross-env NODE_ENV=test jest --verbose --config test/native/jest.config.js", + "test:native": "cross-env NODE_ENV=test jest --config test/native/jest.config.js", "test:native:clean": "jest --clearCache --config test/native/jest.config.js; rm -rf $TMPDIR/jest_*", "test:native:debug": "cross-env NODE_ENV=test node --inspect-brk node_modules/.bin/jest --runInBand --verbose --config test/native/jest.config.js", - "test:native:perf": "cross-env TEST_RUNNER_ARGS='--runInBand --config test/native/jest.config.js --testMatch \"**/performance/*.native.[jt]s?(x)\" --verbose' reassure", - "test:native:perf:baseline": "cross-env TEST_RUNNER_ARGS='--runInBand --config test/native/jest.config.js --testMatch \"**/performance/*.native.[jt]s?(x)\" --verbose' reassure --baseline --testMatch \"**/performance/*.native.[jt]s?(x)\"", + "test:native:perf": "cross-env TEST_RUNNER_ARGS='--runInBand --config test/native/jest.config.js --testMatch \"**/performance/*.native.[jt]s?(x)\"' reassure", + "test:native:perf:baseline": "cross-env TEST_RUNNER_ARGS='--runInBand --config test/native/jest.config.js --testMatch \"**/performance/*.native.[jt]s?(x)\"' reassure --baseline --testMatch \"**/performance/*.native.[jt]s?(x)\"", "test:native:update": "npm run test:native -- --updateSnapshot", "test:performance": "wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js", "test:performance:debug": "wp-scripts --inspect-brk test-e2e --runInBand --no-cache --verbose --config packages/e2e-tests/jest.performance.config.js --puppeteer-devtools", diff --git a/packages/react-native-editor/jest_ui.config.js b/packages/react-native-editor/jest_ui.config.js index 43dc5519ee869f..8e84ae213dfdaf 100644 --- a/packages/react-native-editor/jest_ui.config.js +++ b/packages/react-native-editor/jest_ui.config.js @@ -9,7 +9,6 @@ if ( process.env.TEST_RN_PLATFORM ) { } module.exports = { - verbose: true, rootDir: './', haste: { defaultPlatform: rnPlatform, diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 2bb4ec6a570e48..53339f56fb807b 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -104,9 +104,9 @@ "preios:xcode10": "cd ../../node_modules/react-native && ./scripts/ios-install-third-party.sh && cd third-party/glog-0.3.5 && [ -f libglog.pc ] || ../../scripts/ios-configure-glog.sh", "ios": "react-native run-ios", "ios:fast": "react-native run-ios", - "device-tests": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=3 --testPathIgnorePatterns='canary|gutenberg-editor-rendering' --verbose --config ./jest_ui.config.js", - "device-tests-canary": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=2 --testPathPattern=@canary --verbose --config ./jest_ui.config.js", - "device-tests:local": "cross-env NODE_ENV=test jest --runInBand --detectOpenHandles --verbose --forceExit --config ./jest_ui.config.js", + "device-tests": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=3 --testPathIgnorePatterns='canary|gutenberg-editor-rendering' --config ./jest_ui.config.js", + "device-tests-canary": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=2 --testPathPattern=@canary --config ./jest_ui.config.js", + "device-tests:local": "cross-env NODE_ENV=test jest --runInBand --detectOpenHandles --forceExit --config ./jest_ui.config.js", "device-tests:debug": "cross-env NODE_ENV=test node $NODE_DEBUG_OPTION --inspect-brk node_modules/jest/bin/jest --runInBand --detectOpenHandles --verbose --config ./jest_ui.config.js", "test:e2e:bundle:android": "mkdir -p android/app/src/main/assets && npm run rn-bundle -- --reset-cache --platform android --dev false --minify false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res", "test:e2e:build-app:android": "cd android && ./gradlew clean && ./gradlew assembleDebug", diff --git a/test/native/jest.config.js b/test/native/jest.config.js index 1e6031ee89c775..7ecbf8036a03c4 100644 --- a/test/native/jest.config.js +++ b/test/native/jest.config.js @@ -19,7 +19,6 @@ const transpiledPackageNames = glob( 'packages/*/src/index.{js,ts}' ).map( ); module.exports = { - verbose: true, rootDir: '../../', // Automatically clear mock calls and instances between every test. clearMocks: true, From 52f2bc8195fb72cbcb1ac698393fb99d0d08dee6 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 1 Aug 2023 19:27:36 +0300 Subject: [PATCH 022/150] Editor: Use hooks instead of HoCs in `PostTypeSupportCheck` (#53235) * Editor: Use hooks instead of HoCs in PostTypeSupportCheck * Mock useSelect in tests * Fix more tests --- .../src/components/post-author/test/check.js | 30 ++++++----- .../post-type-support-check/index.js | 18 +++---- .../post-type-support-check/test/index.js | 54 ++++++++++++------- 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/packages/editor/src/components/post-author/test/check.js b/packages/editor/src/components/post-author/test/check.js index 014599500743cc..597ab97ea2a8ad 100644 --- a/packages/editor/src/components/post-author/test/check.js +++ b/packages/editor/src/components/post-author/test/check.js @@ -19,32 +19,38 @@ jest.mock( '@wordpress/data/src/components/use-select', () => { return mock; } ); +function setupUseSelectMock( hasAssignAuthorAction, hasAuthors ) { + useSelect.mockImplementation( ( cb ) => { + return cb( () => ( { + getPostType: () => ( { supports: { author: true } } ), + getEditedPostAttribute: () => {}, + getCurrentPost: () => ( { + _links: { + 'wp:action-assign-author': hasAssignAuthorAction, + }, + } ), + getUsers: () => Array( hasAuthors ? 1 : 0 ).fill( {} ), + } ) ); + } ); +} + describe( 'PostAuthorCheck', () => { it( 'should not render anything if has no authors', () => { - useSelect.mockImplementation( () => ( { - hasAuthors: false, - hasAssignAuthorAction: true, - } ) ); + setupUseSelectMock( false, true ); render( authors ); expect( screen.queryByText( 'authors' ) ).not.toBeInTheDocument(); } ); it( "should not render anything if doesn't have author action", () => { - useSelect.mockImplementation( () => ( { - hasAuthors: true, - hasAssignAuthorAction: false, - } ) ); + setupUseSelectMock( true, false ); render( authors ); expect( screen.queryByText( 'authors' ) ).not.toBeInTheDocument(); } ); it( 'should render control', () => { - useSelect.mockImplementation( () => ( { - hasAuthors: true, - hasAssignAuthorAction: true, - } ) ); + setupUseSelectMock( true, true ); render( authors ); expect( screen.getByText( 'authors' ) ).toBeVisible(); diff --git a/packages/editor/src/components/post-type-support-check/index.js b/packages/editor/src/components/post-type-support-check/index.js index 57a774fc17e422..cf6381eeeccdc5 100644 --- a/packages/editor/src/components/post-type-support-check/index.js +++ b/packages/editor/src/components/post-type-support-check/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; /** @@ -14,7 +14,6 @@ import { store as editorStore } from '../../store'; * type supports one of the given `supportKeys` prop. * * @param {Object} props Props. - * @param {string} [props.postType] Current post type. * @param {WPElement} props.children Children to be rendered if post * type supports. * @param {(string|string[])} props.supportKeys String or string array of keys @@ -22,7 +21,12 @@ import { store as editorStore } from '../../store'; * * @return {WPComponent} The component to be rendered. */ -export function PostTypeSupportCheck( { postType, children, supportKeys } ) { +export function PostTypeSupportCheck( { children, supportKeys } ) { + const postType = useSelect( ( select ) => { + const { getEditedPostAttribute } = select( editorStore ); + const { getPostType } = select( coreStore ); + return getPostType( getEditedPostAttribute( 'type' ) ); + }, [] ); let isSupported = true; if ( postType ) { isSupported = ( @@ -37,10 +41,4 @@ export function PostTypeSupportCheck( { postType, children, supportKeys } ) { return children; } -export default withSelect( ( select ) => { - const { getEditedPostAttribute } = select( editorStore ); - const { getPostType } = select( coreStore ); - return { - postType: getPostType( getEditedPostAttribute( 'type' ) ), - }; -} )( PostTypeSupportCheck ); +export default PostTypeSupportCheck; diff --git a/packages/editor/src/components/post-type-support-check/test/index.js b/packages/editor/src/components/post-type-support-check/test/index.js index c9f9e0ab1ebe36..29e538bd089e7e 100644 --- a/packages/editor/src/components/post-type-support-check/test/index.js +++ b/packages/editor/src/components/post-type-support-check/test/index.js @@ -3,15 +3,37 @@ */ import { render } from '@testing-library/react'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + /** * Internal dependencies */ import { PostTypeSupportCheck } from '../'; +jest.mock( '@wordpress/data/src/components/use-select', () => { + // This allows us to tweak the returned value on each test. + const mock = jest.fn(); + return mock; +} ); + +function setupUseSelectMock( postType ) { + useSelect.mockImplementation( ( cb ) => { + return cb( () => ( { + getPostType: () => postType, + getEditedPostAttribute: () => 'post', + } ) ); + } ); +} + describe( 'PostTypeSupportCheck', () => { it( 'renders its children when post type is not known', () => { + setupUseSelectMock( undefined ); + const { container } = render( - + Supported ); @@ -20,11 +42,11 @@ describe( 'PostTypeSupportCheck', () => { } ); it( 'does not render its children when post type is known and not supports', () => { - const postType = { + setupUseSelectMock( { supports: {}, - }; + } ); const { container } = render( - + Supported ); @@ -33,13 +55,13 @@ describe( 'PostTypeSupportCheck', () => { } ); it( 'renders its children when post type is known and supports', () => { - const postType = { + setupUseSelectMock( { supports: { title: true, }, - }; + } ); const { container } = render( - + Supported ); @@ -48,16 +70,13 @@ describe( 'PostTypeSupportCheck', () => { } ); it( 'renders its children if some of keys supported', () => { - const postType = { + setupUseSelectMock( { supports: { title: true, }, - }; + } ); const { container } = render( - + Supported ); @@ -66,14 +85,11 @@ describe( 'PostTypeSupportCheck', () => { } ); it( 'does not render its children if none of keys supported', () => { - const postType = { + setupUseSelectMock( { supports: {}, - }; + } ); const { container } = render( - + Supported ); From 6bbff4c7198bc74441118ec6185ed43396c811ee Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 1 Aug 2023 21:52:24 +0400 Subject: [PATCH 023/150] Block Editor: Fix ESLint warning for the 'useBlockEditingMode' hook (#53218) --- .../block-editor/src/components/block-editing-mode/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-editing-mode/index.js b/packages/block-editor/src/components/block-editing-mode/index.js index 0347a9b7378d0e..8cb7bdc5d7b868 100644 --- a/packages/block-editor/src/components/block-editing-mode/index.js +++ b/packages/block-editor/src/components/block-editing-mode/index.js @@ -66,6 +66,6 @@ export function useBlockEditingMode( mode ) { unsetBlockEditingMode( clientId ); } }; - }, [ clientId, mode ] ); + }, [ clientId, mode, setBlockEditingMode, unsetBlockEditingMode ] ); return blockEditingMode; } From afc73eec673e59df108571869ce79eca7a7d6981 Mon Sep 17 00:00:00 2001 From: Pooja Killekar <41000648+pooja-muchandikar@users.noreply.github.com> Date: Wed, 2 Aug 2023 01:07:31 +0530 Subject: [PATCH 024/150] Migrate Allowed Block Test to Playwright (#53171) * Migrate Allowed Block Test to Playwright * Refactor test case * Fix Style Lint Issue * Address feedbacks --- .../editor/plugins/allowed-blocks.test.js | 59 ---------------- .../editor/plugins/allowed-blocks.spec.js | 70 +++++++++++++++++++ 2 files changed, 70 insertions(+), 59 deletions(-) delete mode 100644 packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js create mode 100644 test/e2e/specs/editor/plugins/allowed-blocks.spec.js diff --git a/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js deleted file mode 100644 index 804e062fa725b8..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - clickOnMoreMenuItem, - createNewPost, - deactivatePlugin, - searchForBlock, -} from '@wordpress/e2e-test-utils'; - -describe( 'Allowed Blocks Filter', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-allowed-blocks' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-allowed-blocks' ); - } ); - - it( 'should restrict the allowed blocks in the inserter', async () => { - // The paragraph block is available. - await searchForBlock( 'Paragraph' ); - const paragraphBlockButton = ( - await page.$x( `//button//span[contains(text(), 'Paragraph')]` ) - )[ 0 ]; - expect( paragraphBlockButton ).not.toBeNull(); - - // The gallery block is not available. - await searchForBlock( 'Gallery' ); - - const galleryBlockButton = ( - await page.$x( `//button//span[contains(text(), 'Gallery')]` ) - )[ 0 ]; - expect( galleryBlockButton ).toBeUndefined(); - } ); - - it( 'should remove not allowed blocks from the block manager', async () => { - await clickOnMoreMenuItem( 'Preferences' ); - const [ blocksTab ] = await page.$x( - `//button[contains(text(), "Blocks")]` - ); - await blocksTab.click(); - - const BLOCK_LABEL_SELECTOR = - '.edit-post-block-manager__checklist-item .components-checkbox-control__label'; - await page.waitForSelector( BLOCK_LABEL_SELECTOR ); - const blocks = await page.evaluate( ( selector ) => { - return Array.from( document.querySelectorAll( selector ) ) - .map( ( element ) => ( element.innerText || '' ).trim() ) - .sort(); - }, BLOCK_LABEL_SELECTOR ); - expect( blocks ).toEqual( [ 'Image', 'Paragraph' ] ); - } ); -} ); diff --git a/test/e2e/specs/editor/plugins/allowed-blocks.spec.js b/test/e2e/specs/editor/plugins/allowed-blocks.spec.js new file mode 100644 index 00000000000000..4211e428238218 --- /dev/null +++ b/test/e2e/specs/editor/plugins/allowed-blocks.spec.js @@ -0,0 +1,70 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Allowed Blocks Filter', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( 'gutenberg-test-allowed-blocks' ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( 'gutenberg-test-allowed-blocks' ); + } ); + + test( 'should restrict the allowed blocks in the inserter', async ( { + page, + } ) => { + // The paragraph block is available. + await page + .getByRole( 'button', { name: 'Toggle block inserter' } ) + .click(); + + const searchbox = page + .getByRole( 'region', { name: 'Block Library' } ) + .getByRole( 'searchbox', { + name: 'Search for blocks and patterns', + } ); + + await searchbox.fill( 'Paragraph' ); + + await expect( + page.getByRole( 'option', { name: 'Paragraph' } ) + ).toBeVisible(); + + // The gallery block is not available. + await searchbox.click( { + clickCount: 3, + } ); + await page.keyboard.press( 'Backspace' ); + + await searchbox.fill( 'Gallery' ); + + await expect( + page.getByRole( 'option', { name: 'Gallery' } ) + ).toBeHidden(); + } ); + + test( 'should remove not allowed blocks from the block manager', async ( { + page, + } ) => { + await page + .getByRole( 'region', { name: 'Editor top bar' } ) + .getByRole( 'button', { name: 'Options' } ) + .click(); + + await page.getByRole( 'menuitem', { name: 'Preferences' } ).click(); + + await page.getByRole( 'tab', { name: 'Blocks' } ).click(); + + await expect( + page + .getByRole( 'region', { name: 'Available block types' } ) + .getByRole( 'listitem' ) + ).toHaveText( [ 'Paragraph', 'Image' ] ); + } ); +} ); From 76faac8982291440d416fe96d987757658547494 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 2 Aug 2023 13:14:21 +1200 Subject: [PATCH 025/150] Patterns: Show the default patterns icons for all pattern blocks in inserter (#53208) --- packages/block-editor/src/store/selectors.js | 45 +------------------ .../block-editor/src/store/test/selectors.js | 3 +- 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index e459e536c90914..1c9a2c1348ce0c 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1962,51 +1962,8 @@ const buildBlockTypeItem = */ export const getInserterItems = createSelector( ( state, rootClientId = null ) => { - /* - * Matches block comment delimiters amid serialized content. - * - * @see `tokenizer` in `@wordpress/block-serialization-default-parser` - * package - * - * blockParserTokenizer differs from the original tokenizer in the - * following ways: - * - * - removed global flag (/g) - * - prepended ^\s* - * - */ - const blockParserTokenizer = - /^\s*)[^])*)\5|[^]*?)}\s+)?(\/)?-->/; - const buildReusableBlockInserterItem = ( reusableBlock ) => { - let icon = symbol; - - /* - * Instead of always displaying a generic "symbol" icon for every - * reusable block, try to use an icon that represents the first - * outermost block contained in the reusable block. This requires - * scanning the serialized form of the reusable block to find its - * first block delimiter, then looking up the corresponding block - * type, if available. - */ - if ( Platform.OS === 'web' ) { - const content = - typeof reusableBlock.content.raw === 'string' - ? reusableBlock.content.raw - : reusableBlock.content; - const rawBlockMatch = content.match( blockParserTokenizer ); - if ( rawBlockMatch ) { - const [ , , namespace = 'core/', blockName ] = - rawBlockMatch; - const referencedBlockType = getBlockType( - namespace + blockName - ); - if ( referencedBlockType ) { - icon = referencedBlockType.icon; - } - } - } - + const icon = symbol; const id = `core/block/${ reusableBlock.id }`; const { time, count = 0 } = getInsertUsage( state, id ) || {}; const frecency = calculateFrecency( time, count ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 9140248391cf8e..fd10f9189a4ef3 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -7,6 +7,7 @@ import { setFreeformContentHandlerName, } from '@wordpress/blocks'; import { RawHTML } from '@wordpress/element'; +import { symbol } from '@wordpress/icons'; /** * Internal dependencies @@ -3347,7 +3348,7 @@ describe( 'selectors', () => { category: 'reusable', content: '', frecency: 0, - icon: { src: 'test' }, + icon: symbol, id: 'core/block/1', initialAttributes: { ref: 1 }, isDisabled: false, From e89860151d529342ea28757393e44df3e84fe24f Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:36:16 +1000 Subject: [PATCH 026/150] Docs: Clarify that blockGap support depends on layout support (#53254) --- docs/how-to-guides/themes/theme-json.md | 2 +- docs/reference-guides/block-api/block-supports.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 697592adf3236c..418e076fa839a7 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -1331,7 +1331,7 @@ As a result of this change, it’s now the block author and theme author’s res ### What is blockGap and how can I use it? -For blocks that contain inner blocks, such as Group, Columns, Buttons, and Social Icons, `blockGap` controls the spacing between inner blocks. Depending on the layout of the block, the `blockGap` value will be output as either a vertical margin or a `gap` value. In the editor, the control for the `blockGap` value is called _Block spacing_, located in the Dimensions panel. +For blocks that contain inner blocks, such as Group, Columns, Buttons, and Social Icons, `blockGap` controls the spacing between inner blocks. For `blockGap` to work, the block must also opt in to the [`layout` block support](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/#layout), which provides layout styles that can be adjusted via the block spacing controls. Depending on the layout of the block, the `blockGap` value will be output as either a vertical margin or a `gap` value. In the editor, the control for the `blockGap` value is called _Block spacing_, located in the Dimensions panel. ```json { diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 0995f4d86cfd76..a888ea6caf0c0f 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -710,7 +710,7 @@ supports: { spacing: { margin: true, // Enable margin UI control. padding: true, // Enable padding UI control. - blockGap: true, // Enables block spacing UI control. + blockGap: true, // Enables block spacing UI control for blocks that also use `layout`. } } ``` From 697d1ba95be5be61ee7fb95d19bc3c0dfc917ccf Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 2 Aug 2023 07:37:52 +0400 Subject: [PATCH 027/150] Temporarily skip widget import e2e test (#53226) --- test/e2e/specs/site-editor/template-part.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/specs/site-editor/template-part.spec.js b/test/e2e/specs/site-editor/template-part.spec.js index b6c124f2fbd3c6..cd81a616b1fee1 100644 --- a/test/e2e/specs/site-editor/template-part.spec.js +++ b/test/e2e/specs/site-editor/template-part.spec.js @@ -293,7 +293,8 @@ test.describe( 'Template Part', () => { await expect( paragraph ).toBeVisible(); } ); - test( 'can import a widget area into an empty template part', async ( { + // Reason: https://github.com/WordPress/gutenberg/issues/47003. + test.skip( 'can import a widget area into an empty template part', async ( { admin, editor, page, From d71f08d975604b50f4a8ba03855fc333c18f879e Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:43:18 +0900 Subject: [PATCH 028/150] Fix: Snack bar not fixed on certain pages in the Site Editor (#53207) --- packages/base-styles/_z-index.scss | 1 + .../src/components/page-patterns/style.scss | 8 -------- packages/edit-site/src/components/page/index.js | 16 ++++++++-------- .../edit-site/src/components/page/style.scss | 13 ++++++++----- .../edit-site/src/components/table/style.scss | 1 + 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index f999d3bb01dec0..5766ad44a8205b 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -189,6 +189,7 @@ $z-layers: ( ".edit-site-layout__hub": 3, ".edit-site-layout__header": 2, ".edit-site-page-header": 2, + ".edit-site-page-content": 1, ".edit-site-patterns__header": 2, ".edit-site-patterns__grid-pagination": 2, ".edit-site-layout__canvas-container": 2, diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index 400cc36aeaa43c..0312803323863f 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -6,14 +6,6 @@ padding: 0; overflow-x: auto; - .edit-site-page-content { - height: 100%; - position: relative; - padding: 0; - display: flex; - flex-flow: column; - } - .components-base-control { width: 100%; @include break-medium { diff --git a/packages/edit-site/src/components/page/index.js b/packages/edit-site/src/components/page/index.js index a713989a3ce0a2..02d0bd2e746eec 100644 --- a/packages/edit-site/src/components/page/index.js +++ b/packages/edit-site/src/components/page/index.js @@ -26,17 +26,17 @@ export default function Page( { return ( - { ! hideTitleFromUI && title && ( -
- ) }
+ { ! hideTitleFromUI && title && ( +
+ ) } { children } -
+ ); } diff --git a/packages/edit-site/src/components/page/style.scss b/packages/edit-site/src/components/page/style.scss index e29849e408c0d5..8da7df8e0385b8 100644 --- a/packages/edit-site/src/components/page/style.scss +++ b/packages/edit-site/src/components/page/style.scss @@ -2,7 +2,7 @@ color: $gray-800; background: $white; flex-grow: 1; - overflow: auto; + overflow: hidden; margin: 0; margin-top: $header-height; @include break-medium() { @@ -13,8 +13,7 @@ .edit-site-page-header { padding: 0 $grid-unit-40; - height: $header-height; - padding-left: $grid-unit-40; + min-height: $header-height; border-bottom: 1px solid $gray-100; background: $white; position: sticky; @@ -33,6 +32,10 @@ } .edit-site-page-content { - padding: $grid-unit-40 $grid-unit-40; - overflow-x: auto; + height: 100%; + display: flex; + overflow: auto; + flex-flow: column; + position: relative; + z-index: z-index(".edit-site-page-content"); } diff --git a/packages/edit-site/src/components/table/style.scss b/packages/edit-site/src/components/table/style.scss index a53b2c836f54cc..85c741575d5a44 100644 --- a/packages/edit-site/src/components/table/style.scss +++ b/packages/edit-site/src/components/table/style.scss @@ -1,5 +1,6 @@ .edit-site-table-wrapper { width: 100%; + padding: $grid-unit-40; } .edit-site-table { From dc6bd913d5023752c899cafe97556228e7e7dd7c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 2 Aug 2023 08:22:31 +0400 Subject: [PATCH 029/150] Block Editor: Simplify check in 'withBlockControls' styles hook (#53227) * Block Editor: Simplify check in 'withBlockControls' styles hook * Set the key prop on 'BlockEdit' to avoid accidental remounting --- packages/block-editor/src/hooks/style.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 138867e9c462c2..b416c86405e512 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -11,7 +11,6 @@ import { addFilter } from '@wordpress/hooks'; import { getBlockSupport, hasBlockSupport, - getBlockType, __EXPERIMENTAL_ELEMENTS as ELEMENTS, } from '@wordpress/blocks'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; @@ -45,8 +44,8 @@ const styleSupportKeys = [ SPACING_SUPPORT_KEY, ]; -const hasStyleSupport = ( blockType ) => - styleSupportKeys.some( ( key ) => hasBlockSupport( blockType, key ) ); +const hasStyleSupport = ( nameOrType ) => + styleSupportKeys.some( ( key ) => hasBlockSupport( nameOrType, key ) ); /** * Returns the inline styles to add depending on the style object @@ -348,9 +347,8 @@ export function addEditProps( settings ) { */ export const withBlockControls = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const blockType = getBlockType( props.name ); - if ( ! hasStyleSupport( blockType ) ) { - return ; + if ( ! hasStyleSupport( props.name ) ) { + return ; } const shouldDisplayControls = useDisplayBlockControls(); @@ -366,7 +364,7 @@ export const withBlockControls = createHigherOrderComponent( ) } - + ); }, From ba7be1d9120e7654b07dfbcef52281106e9fb9e9 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:27:21 +0300 Subject: [PATCH 030/150] Components: Introduce a basic ProgressBar component (#53030) * Components: Introduce a basic ProgressBar * Add a CHANGELOG entry * Expose as a private API * Add missing className in type * Render progressbar element invisibly * Update appearance * Mark the component story as experimental Co-authored-by: Lena Morita * Add an experimental alert in the component README Co-authored-by: Lena Morita * Remove usage from README * Remove README from manifest * Add border radius to indicator as well * Refactor from SCSS to Emotion * Remove color props * Update tests * Add an aria-label to the underlying progress element * Update snapshots * Intentionally ignore ProgressBar README from docs manifest * Use the components gray color variable Co-authored-by: Lena Morita * Add TODO for using the :indeterminate pseudo-class * Remove default className * Add a max-width * Update snapshots * Add support for ID and ref * Fix docs * Use emotion for indeterminate and value-based styling * Remove ID, pass rest props down to the progress element --------- Co-authored-by: James Koster Co-authored-by: Lena Morita --- docs/tool/manifest.js | 1 + packages/components/CHANGELOG.md | 4 + packages/components/src/private-apis.ts | 2 + .../components/src/progress-bar/README.md | 30 +++++++ .../components/src/progress-bar/index.tsx | 45 +++++++++++ .../src/progress-bar/stories/index.tsx | 33 ++++++++ .../components/src/progress-bar/styles.ts | 67 ++++++++++++++++ .../src/progress-bar/test/index.tsx | 79 +++++++++++++++++++ packages/components/src/progress-bar/types.ts | 11 +++ 9 files changed, 272 insertions(+) create mode 100644 packages/components/src/progress-bar/README.md create mode 100644 packages/components/src/progress-bar/index.tsx create mode 100644 packages/components/src/progress-bar/stories/index.tsx create mode 100644 packages/components/src/progress-bar/styles.ts create mode 100644 packages/components/src/progress-bar/test/index.tsx create mode 100644 packages/components/src/progress-bar/types.ts diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index d3ed5d61dc0bb1..b061fa5149c698 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -15,6 +15,7 @@ const componentPaths = glob( 'packages/components/src/*/**/README.md', { 'packages/components/src/theme/README.md', 'packages/components/src/view/README.md', 'packages/components/src/dropdown-menu-v2/README.md', + 'packages/components/src/progress-bar/README.md', ], } ); const packagePaths = glob( 'packages/*/package.json' ) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2cfc0c1f5caeba..9dfc79d11f757f 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Add a new `ProgressBar` component. ([#53030](https://github.com/WordPress/gutenberg/pull/53030)). + ### Enhancements - `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)). diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index 641ba5c26d40b6..b144f348143c60 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -8,6 +8,7 @@ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/pri */ import { default as CustomSelectControl } from './custom-select-control'; import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils'; +import { default as ProgressBar } from './progress-bar'; import { createPrivateSlotFill } from './slot-fill'; import { DropdownMenu as DropdownMenuV2, @@ -45,4 +46,5 @@ lock( privateApis, { DropdownMenuSeparatorV2, DropdownSubMenuV2, DropdownSubMenuTriggerV2, + ProgressBar, } ); diff --git a/packages/components/src/progress-bar/README.md b/packages/components/src/progress-bar/README.md new file mode 100644 index 00000000000000..d0f62ce7e3be57 --- /dev/null +++ b/packages/components/src/progress-bar/README.md @@ -0,0 +1,30 @@ +# ProgressBar + +
+This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. +
+ +A simple horizontal progress bar component. + +Supports two modes: determinate and indeterminate. A progress bar is determinate when a specific progress value has been specified (from 0 to 100), and indeterminate when a value hasn't been specified. + +### Props + +The component accepts the following props: + +#### `value`: `number` + +The progress value, a number from 0 to 100. +If a `value` is not specified, the progress bar will be considered indeterminate. + +- Required: No + +##### `className`: `string` + +A CSS class to apply to the underlying `div` element, serving as a progress bar track. + +- Required: No + +#### Inherited props + +Any additional props will be passed the underlying `` element. diff --git a/packages/components/src/progress-bar/index.tsx b/packages/components/src/progress-bar/index.tsx new file mode 100644 index 00000000000000..16f2630953840a --- /dev/null +++ b/packages/components/src/progress-bar/index.tsx @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import type { ForwardedRef } from 'react'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as ProgressBarStyled from './styles'; +import type { ProgressBarProps } from './types'; +import type { WordPressComponentProps } from '../ui/context'; + +function UnforwardedProgressBar( + props: WordPressComponentProps< ProgressBarProps, 'progress', false >, + ref: ForwardedRef< HTMLProgressElement > +) { + const { className, value, ...progressProps } = props; + const isIndeterminate = ! Number.isFinite( value ); + + return ( + + + + + ); +} + +export const ProgressBar = forwardRef( UnforwardedProgressBar ); + +export default ProgressBar; diff --git a/packages/components/src/progress-bar/stories/index.tsx b/packages/components/src/progress-bar/stories/index.tsx new file mode 100644 index 00000000000000..1499d68a10bbbd --- /dev/null +++ b/packages/components/src/progress-bar/stories/index.tsx @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * Internal dependencies + */ +import { ProgressBar } from '..'; + +const meta: ComponentMeta< typeof ProgressBar > = { + component: ProgressBar, + title: 'Components (Experimental)/ProgressBar', + argTypes: { + value: { control: { type: 'number', min: 0, max: 100, step: 1 } }, + }, + parameters: { + controls: { + expanded: true, + }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +const Template: ComponentStory< typeof ProgressBar > = ( { ...args } ) => { + return ; +}; + +export const Default: ComponentStory< typeof ProgressBar > = Template.bind( + {} +); +Default.args = {}; diff --git a/packages/components/src/progress-bar/styles.ts b/packages/components/src/progress-bar/styles.ts new file mode 100644 index 00000000000000..194c88c4e901b3 --- /dev/null +++ b/packages/components/src/progress-bar/styles.ts @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +import { css, keyframes } from '@emotion/react'; + +/** + * Internal dependencies + */ +import { COLORS, CONFIG } from '../utils'; + +const animateProgressBar = keyframes( { + '0%': { + left: '-50%', + }, + '100%': { + left: '100%', + }, +} ); + +// Width of the indicator for the indeterminate progress bar +export const INDETERMINATE_TRACK_WIDTH = 50; + +export const Track = styled.div` + position: relative; + overflow: hidden; + width: 100%; + max-width: 160px; + height: ${ CONFIG.borderWidthFocus }; + background-color: var( + --wp-components-color-gray-100, + ${ COLORS.gray[ 100 ] } + ); + border-radius: ${ CONFIG.radiusBlockUi }; +`; + +export const Indicator = styled.div< { + isIndeterminate: boolean; + value?: number; +} >` + display: inline-block; + position: absolute; + top: 0; + height: 100%; + border-radius: ${ CONFIG.radiusBlockUi }; + background-color: ${ COLORS.ui.theme }; + + ${ ( { isIndeterminate, value } ) => + isIndeterminate + ? css( { + animationDuration: '1.5s', + animationTimingFunction: 'ease-in-out', + animationIterationCount: 'infinite', + animationName: animateProgressBar, + width: `${ INDETERMINATE_TRACK_WIDTH }%`, + } ) + : css( { width: `${ value }%` } ) }; +`; + +export const ProgressElement = styled.progress` + position: absolute; + top: 0; + left: 0; + opacity: 0; + width: 100%; + height: 100%; +`; diff --git a/packages/components/src/progress-bar/test/index.tsx b/packages/components/src/progress-bar/test/index.tsx new file mode 100644 index 00000000000000..425c54ce8a3022 --- /dev/null +++ b/packages/components/src/progress-bar/test/index.tsx @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import { ProgressBar } from '..'; +import { INDETERMINATE_TRACK_WIDTH } from '../styles'; + +describe( 'ProgressBar', () => { + it( 'should render an indeterminate semantic progress bar element', () => { + render( ); + + const progressBar = screen.getByRole( 'progressbar' ); + + expect( progressBar ).toBeInTheDocument(); + expect( progressBar ).not.toBeVisible(); + expect( progressBar ).not.toHaveValue(); + } ); + + it( 'should render a determinate semantic progress bar element', () => { + render( ); + + const progressBar = screen.getByRole( 'progressbar' ); + + expect( progressBar ).toBeInTheDocument(); + expect( progressBar ).not.toBeVisible(); + expect( progressBar ).toHaveValue( 55 ); + } ); + + it( 'should use `INDETERMINATE_TRACK_WIDTH`% as track width for indeterminate progress bar', () => { + const { container } = render( ); + + /** + * We're intentionally not using an accessible selector, because + * the track is an intentionally non-interactive presentation element. + */ + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const indicator = container.firstChild?.firstChild; + + expect( indicator ).toHaveStyle( { + width: `${ INDETERMINATE_TRACK_WIDTH }%`, + } ); + } ); + + it( 'should use `value`% as width for determinate progress bar', () => { + const { container } = render( ); + + /** + * We're intentionally not using an accessible selector, because + * the track is an intentionally non-interactive presentation element. + */ + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const indicator = container.firstChild?.firstChild; + + expect( indicator ).toHaveStyle( { + width: '55%', + } ); + } ); + + it( 'should pass any additional props down to the underlying `progress` element', () => { + const id = 'foo-bar-123'; + const ariaLabel = 'in progress...'; + const style = { opacity: 1 }; + + render( + + ); + + expect( screen.getByRole( 'progressbar' ) ).toHaveAttribute( 'id', id ); + expect( screen.getByRole( 'progressbar' ) ).toHaveAttribute( + 'aria-label', + ariaLabel + ); + expect( screen.getByRole( 'progressbar' ) ).toHaveStyle( style ); + } ); +} ); diff --git a/packages/components/src/progress-bar/types.ts b/packages/components/src/progress-bar/types.ts new file mode 100644 index 00000000000000..9beb28317e58aa --- /dev/null +++ b/packages/components/src/progress-bar/types.ts @@ -0,0 +1,11 @@ +export type ProgressBarProps = { + /** + * Value of the progress bar. + */ + value?: number; + + /** + * A CSS class to apply to the progress bar wrapper (track) element. + */ + className?: string; +}; From a6d6b9cdefb6901e0843c0eb7938b6e6ff9279e8 Mon Sep 17 00:00:00 2001 From: James Koster Date: Wed, 2 Aug 2023 09:40:26 +0100 Subject: [PATCH 031/150] Update document title buttons radius (#53221) --- .../components/header-edit-mode/document-actions/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss b/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss index a5a182cf7426fd..cfaed598304055 100644 --- a/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss +++ b/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss @@ -1,7 +1,6 @@ .edit-site-document-actions { display: flex; align-items: center; - gap: $grid-unit; height: $button-size; justify-content: space-between; // Flex items will, by default, refuse to shrink below a minimum @@ -10,7 +9,7 @@ // See https://dev.w3.org/csswg/css-flexbox/#min-size-auto min-width: 0; background: $gray-100; - border-radius: 4px; + border-radius: $grid-unit-05; width: min(100%, 450px); // Make the document title shorter in top-toolbar mode, as it has to be covered. @@ -19,6 +18,8 @@ } .components-button { + border-radius: $grid-unit-05; + &:hover { color: var(--wp-block-synced-color); background: $gray-200; From 5bf6aa089c67685da4aa738a45d30ed32786fb73 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Wed, 2 Aug 2023 11:13:39 +0200 Subject: [PATCH 032/150] Interactivity API: remove the `wp-show` directive (#53240) * Remove wp-show * Remove wp-show e2e tests * Replace wp-show with wp-fakeshow in the rest of the e2e tests * Update changelog * Update changelog with PR number * Change fakeshow with show-mock --- .../directive-effect/render.php | 2 +- .../directive-effect/view.js | 23 +++----- .../directive-show/block.json | 14 ----- .../directive-show/render.php | 53 ----------------- .../interactive-blocks/directive-show/view.js | 24 -------- .../tovdom-islands/render.php | 10 ++-- .../interactive-blocks/tovdom-islands/view.js | 19 +++++- packages/interactivity/CHANGELOG.md | 4 ++ packages/interactivity/src/directives.js | 20 ------- .../interactivity/directives-show.spec.ts | 59 ------------------- 10 files changed, 35 insertions(+), 193 deletions(-) delete mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-show/block.json delete mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-show/render.php delete mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-show/view.js delete mode 100644 test/e2e/specs/interactivity/directives-show.spec.ts diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-effect/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-effect/render.php index 243826aae35046..440edbf5881f68 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-effect/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-effect/render.php @@ -7,7 +7,7 @@ ?>
-
+
{ - const { store, directive, useContext, useMemo } = wp.interactivity; + const { store, directive } = wp.interactivity; - // Fake `data-wp-fakeshow` directive to test when things are removed from the DOM. - // Replace with `data-wp-show` when it's ready. + // Fake `data-wp-show-mock` directive to test when things are removed from the + // DOM. Replace with `data-wp-show` when it's ready. directive( - 'fakeshow', + 'show-mock', ( { directives: { - fakeshow: { default: fakeshow }, + "show-mock": { default: showMock }, }, element, evaluate, - context, } ) => { - const contextValue = useContext( context ); - const children = useMemo( - () => - element.type === 'template' - ? element.props.templateChildren - : element, - [] - ); - if ( ! evaluate( fakeshow, { context: contextValue } ) ) return null; - return children; + if ( ! evaluate( showMock ) ) return null; + return element; } ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-show/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-show/block.json deleted file mode 100644 index ab6801843a7cae..00000000000000 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-show/block.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "apiVersion": 2, - "name": "test/directive-show", - "title": "E2E Interactivity tests - directive show", - "category": "text", - "icon": "heart", - "description": "", - "supports": { - "interactivity": true - }, - "textdomain": "e2e-interactivity", - "viewScript": "directive-show-view", - "render": "file:./render.php" -} diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-show/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-show/render.php deleted file mode 100644 index 5818f3b7c10b63..00000000000000 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-show/render.php +++ /dev/null @@ -1,53 +0,0 @@ - -
- - - - -
-

trueValue children

-
- -
-

falseValue children

-
- -
-
- falseValue -
- -
-
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-show/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-show/view.js deleted file mode 100644 index 9398321b4cfe43..00000000000000 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-show/view.js +++ /dev/null @@ -1,24 +0,0 @@ -( ( { wp } ) => { - /** - * WordPress dependencies - */ - const { store } = wp.interactivity; - - store( { - state: { - trueValue: true, - falseValue: false, - }, - actions: { - toggleTrueValue: ( { state } ) => { - state.trueValue = ! state.trueValue; - }, - toggleFalseValue: ( { state } ) => { - state.falseValue = ! state.falseValue; - }, - toggleContextFalseValue: ( { context } ) => { - context.falseValue = ! context.falseValue; - }, - }, - } ); -} )( window ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php index aa0f2e68d3b7cf..a3ebb7a87424e4 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php @@ -7,14 +7,14 @@ ?>
-
+
This should be shown because it is inside an island.
-
+
This should not be shown because it is inside an island. @@ -23,7 +23,7 @@
-
+
@@ -37,7 +37,7 @@
@@ -51,7 +51,7 @@
-
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/view.js b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/view.js index 3ccb09203e6bb6..f897368193ea8b 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/view.js +++ b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/view.js @@ -1,5 +1,22 @@ ( ( { wp } ) => { - const { store } = wp.interactivity; + const { store, directive, createElement } = wp.interactivity; + + // Fake `data-wp-show-mock` directive to test when things are removed from the + // DOM. Replace with `data-wp-show` when it's ready. + directive( + 'show-mock', + ( { + directives: { + "show-mock": { default: showMock }, + }, + element, + evaluate, + } ) => { + if ( ! evaluate( showMock ) ) + element.props.children = + createElement( "template", null, element.props.children ); + } + ); store( { state: { diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index 7bf3a829becdb6..5a79ee8f5af75c 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Change + +- Remove the `wp-show` directive until we figure out its final implementation. ([#53240](https://github.com/WordPress/gutenberg/pull/53240)) + ## 1.2.0 (2023-07-20) ### New Features diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js index e32d691c29fed4..a25e83c170e8d0 100644 --- a/packages/interactivity/src/directives.js +++ b/packages/interactivity/src/directives.js @@ -244,26 +244,6 @@ export default () => { } ); - // data-wp-show - directive( - 'show', - ( { - directives: { - show: { default: show }, - }, - element, - evaluate, - context, - } ) => { - const contextValue = useContext( context ); - - if ( ! evaluate( show, { context: contextValue } ) ) - element.props.children = ( - - ); - } - ); - // data-wp-ignore directive( 'ignore', diff --git a/test/e2e/specs/interactivity/directives-show.spec.ts b/test/e2e/specs/interactivity/directives-show.spec.ts deleted file mode 100644 index 7e25332ae143c5..00000000000000 --- a/test/e2e/specs/interactivity/directives-show.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Internal dependencies - */ -import { test, expect } from './fixtures'; - -test.describe( 'data-wp-show', () => { - test.beforeAll( async ( { interactivityUtils: utils } ) => { - await utils.activatePlugins(); - await utils.addPostWithBlock( 'test/directive-show' ); - } ); - test.beforeEach( async ( { interactivityUtils: utils, page } ) => { - await page.goto( utils.getLink( 'test/directive-show' ) ); - } ); - test.afterAll( async ( { interactivityUtils: utils } ) => { - await utils.deactivatePlugins(); - await utils.deleteAllPosts(); - } ); - - test( 'show if callback returns truthy value', async ( { page } ) => { - const el = page.getByTestId( 'show if callback returns truthy value' ); - await expect( el ).toBeVisible(); - } ); - - test( 'do not show if callback returns falsy value', async ( { page } ) => { - const el = page.getByTestId( - 'do not show if callback returns false value' - ); - await expect( el ).toBeHidden(); - } ); - - test( 'hide when toggling truthy value to falsy', async ( { page } ) => { - const el = page.getByTestId( 'show if callback returns truthy value' ); - await expect( el ).toBeVisible(); - await page.getByTestId( 'toggle trueValue' ).click(); - await expect( el ).toBeHidden(); - await page.getByTestId( 'toggle trueValue' ).click(); - await expect( el ).toBeVisible(); - } ); - - test( 'show when toggling false value to truthy', async ( { page } ) => { - const el = page.getByTestId( - 'do not show if callback returns false value' - ); - await expect( el ).toBeHidden(); - await page.getByTestId( 'toggle falseValue' ).click(); - await expect( el ).toBeVisible(); - await page.getByTestId( 'toggle falseValue' ).click(); - await expect( el ).toBeHidden(); - } ); - - test( 'can use context values', async ( { page } ) => { - const el = page.getByTestId( 'can use context values' ); - await expect( el ).toBeHidden(); - await page.getByTestId( 'toggle context false value' ).click(); - await expect( el ).toBeVisible(); - await page.getByTestId( 'toggle context false value' ).click(); - await expect( el ).toBeHidden(); - } ); -} ); From 589896872d9fa45993a78dcc344d79d392f49f5f Mon Sep 17 00:00:00 2001 From: JuanMa Date: Wed, 2 Aug 2023 12:35:14 +0100 Subject: [PATCH 033/150] API Reference Docs for Interactivity API (#52948) * first version of the doc * API reference first version * improved visibility of categories and better heading * added selectors definition * added example selectors * wp-efect solo and several examples * better headings * better dom-directives definition * updates based on Luis' feedback * details with code blocks * added table of contents * improved directives intro * effects run callbacks * minor adjustments * better effect execution definition * fixed spaces example * fixed typo wp-style * better html spacing * fixed typo wp-init * fixed typo desctiption wp-init * fixed spacing example * last modifications based on feedback received * fixed indentation * improved examples local-global state * removed unnecesary space --- .../interactivity/docs/2-api-reference.md | 661 ++++++++++++++++++ .../docs/assets/state-directives.png | Bin 0 -> 339655 bytes .../docs/assets/store-server-client.png | Bin 0 -> 158950 bytes 3 files changed, 661 insertions(+) create mode 100644 packages/interactivity/docs/2-api-reference.md create mode 100644 packages/interactivity/docs/assets/state-directives.png create mode 100644 packages/interactivity/docs/assets/store-server-client.png diff --git a/packages/interactivity/docs/2-api-reference.md b/packages/interactivity/docs/2-api-reference.md new file mode 100644 index 00000000000000..249e92f76c093d --- /dev/null +++ b/packages/interactivity/docs/2-api-reference.md @@ -0,0 +1,661 @@ +# API Reference + +To add interactivity to blocks using the Interactivity API, developers can use: + +- **Directives** - added to the markup to add specific behavior to the DOM elements of block. +- **Store** - that contains the logic and data (state, actions, or effects among others) needed for the behaviour. + +DOM elements are connected to data stored in the state & context through directives. If data in the state or context change, directives will react to those changes updating the DOM accordingly (see [diagram](https://excalidraw.com/#json=rEg5d71O_jy3NrgYJUIVd,yjOUmMvxzNf6alqFjElvIw)). + +![State & Directives](assets/state-directives.png) + +## Table of Contents + +- [The directives](#the-directives) + - [List of Directives](#list-of-directives) + - [`wp-interactive`](#wp-interactive) ![](https://img.shields.io/badge/DECLARATIVE-afd2e3.svg) + - [`wp-context`](#wp-context) ![](https://img.shields.io/badge/STATE-afd2e3.svg) + - [`wp-bind`](#wp-bind) ![](https://img.shields.io/badge/ATTRIBUTES-afd2e3.svg) + - [`wp-class`](#wp-class) ![](https://img.shields.io/badge/ATTRIBUTES-afd2e3.svg) + - [`wp-style`](#wp-style) ![](https://img.shields.io/badge/ATTRIBUTES-afd2e3.svg) + - [`wp-text`](#wp-text) ![](https://img.shields.io/badge/CONTENT-afd2e3.svg) + - [`wp-on`](#wp-on) ![](https://img.shields.io/badge/EVENT_HANDLERS-afd2e3.svg) + - [`wp-effect`](#wp-effect) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg) + - [`wp-init`](#wp-init) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg) + - [Values of directives are references to store properties](#values-of-directives-are-references-to-store-properties) +- [The store](#the-store) + - [Elements of the store](#elements-of-the-store) + - [State](#state) + - [Actions](#actions) + - [Effects](#effects) + - [Selectors](#selectors) + - [Arguments passed to callbacks](#arguments-passed-to-callbacks) + - [Setting the store](#setting-the-store) + - [On the client side](#on-the-client-side) + - [On the server side](#on-the-server-side) + + + +## The directives + +Directives are custom attributes that are added to the markup of your block to add behaviour to its DOM elements. This can be done in the `render.php` file (for dynamic blocks) or the `save.js` file (for static blocks). + +Interactivity API directives use the `data-` prefix. + +_Example of directives used in the HTML markup_ + +```html +
+ + +

+ This element is now visible! +

+
+``` + +> **Note** +> The use of Namespaces to define the context, state or any other elements of the store is highly recommended to avoid possible collision with other elements with the same name. In the following examples we have not used namespaces for the sake of simplicity. + +Directives can also be injected dynamically using the [HTML Tag Processor](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2). + +### List of Directives + +With directives we can manage directly in the DOM behavior related to things such as side effects, state, event handlers, attributes or content. + +#### `wp-interactive` + +The `wp-interactive` directive "activates" the interactivity for the DOM element and its children through the Interactivity API (directives and store). + +```html + +
+

I'm interactive now, >and I can use directives!

+
+

I'm also interactive, and I can also use directives!

+
+
+``` + +> **Note** +> The use of `wp-interactive` is a requirement for the Interactivity API "engine" to work. In the following examples the `wp-interactive` has not been added for the sake of simplicity. + + +#### `wp-context` + +It provides **local** state available to a specific HTML node and its children. + +The `wp-context` directive accepts a stringified JSON as value. + +_Example of `wp-context` directive_ +```php +//render.php +
+ +
+``` + +
+ See store used with the directive above + +```js +store( { + actions: { + logId: ( { context } ) => { + console.log( context.post.id ); + }, + }, +} ); +``` + +
+
+ +Different contexts can be defined at different levels and deeper levels will merge their own context with any parent one: + +```html +
+ + +
+ + +
+ +
+ +
+
+``` + +#### `wp-bind` + +It allows setting HTML attributes on elements based on a boolean or string value. + +> This directive follows the syntax `data-wp-bind--attribute`. + +_Example of `wp-bind` directive_ +```html +
  • + +
    + Title +
      + SUBMENU ITEMS +
    +
    +
  • +``` +
    + See store used with the directive above + +```js +store( { + actions: { + toggleMenu: ( { context } ) => { + context.isMenuOpen = !context.isMenuOpen; + }, + }, +} ); +``` + +
    +
    + +The `wp-bind` directive is executed: + - when the element is created. + - each time there's a change on any of the properties of the `state` or `context` involved on getting the final value of the directive (inside the callback or the expression passed as reference). + +When `wp-bind` directive references a callback to get its final value: +- The `wp-bind` directive will be executed each time there's a change on any of the properties of the `state` or `context` used inside this callback. +- The callback receives the attribute name: `attribute`. +- The returned value in the callback function is used to change the value of the associated attribute. + +The `wp-bind` will do different things over the DOM element is applied depending on its value: + - If the value is `true`, the attribute is added: `
    `. + - If the value is `false`, the attribute is removed: `
    `. + - If the value is a string, the attribute is added with its value assigned: `
    `. + +#### `wp-class` + +It adds or removes a class to an HTML element, depending on a boolean value. + +> This directive follows the syntax `data-wp-class--classname`. + +_Example of `wp-class` directive_ +```php +
    +
  • + Option 1 +
  • +
  • + Option 2 +
  • +
    +``` + +
    + See store used with the directive above + +```js +store( { + actions: { + toggleSelection: ( { context } ) => { + context.isSelected = !context.isSelected + } + } +} ); +``` + +
    +
    + +The `wp-class` directive is executed: + - when the element is created. + - each time there's a change on any of the properties of the `state` or `context` involved on getting the final value of the directive (inside the callback or the expression passed as reference). + +When `wp-class` directive references a callback to get its final boolean value, the callback receives the class name: `className`. + +The boolean value received by the directive is used to toggle (add when `true` or remove when `false`) the associated class name from the `class` attribute. + + +#### `wp-style` + +It adds or removes inline style to an HTML element, depending on its value. + +> This directive follows the syntax `data-wp-style--css-property`. + +_Example of `wp-style` directive_ +```html +
    + +

    Hello World!

    +
    +> +``` + +
    + See store used with the directive above + +```js +store( { + actions: { + toggleContextColor: ( { context } ) => { + context.color = context.color === 'red' ? 'blue' : 'red'; + }, + }, +} ); +``` + +
    +
    + +The `wp-style` directive is executed: + - when the element is created. + - each time there's a change on any of the properties of the `state` or `context` involved on getting the final value of the directive (inside the callback or the expression passed as reference). + +When `wp-style` directive references a callback to get its final value, the callback receives the class style property: `css-property`. + +The value received by the directive is used to add or remove the style attribute with the associated CSS property: : + - If the value is `false`, the style attribute is removed: `
    `. + - If the value is a string, the attribute is added with its value assigned: `
    `. + +#### `wp-text` + +It sets the inner text of an HTML element. + +```html +
    + + +
    +``` + +
    + See store used with the directive above + +```js +store( { + actions: { + toggleContextText: ( { context } ) => { + context.text = context.text === 'Text 1' ? 'Text 2' : 'Text 1'; + }, + }, +} ); +``` + +
    +
    + +The `wp-text` directive is executed: + - when the element is created. + - each time there's a change on any of the properties of the `state` or `context` involved on getting the final value of the directive (inside the callback or the expression passed as reference). + +The returned value is used to change the inner content of the element: `
    value
    `. + +#### `wp-on` + +It runs code on dispatched DOM events like `click` or `keyup`. + +> The syntax of this directive is `data-wp-on--[event]` (like `data-wp-on--click` or `data-wp-on--keyup`). + +_Example of `wp-on` directive_ +```php + +``` + +
    + See store used with the directive above + +```js +store( { + actions: { + logTime: () => console.log( new Date() ), + }, +} ); +``` + +
    +
    + +The `wp-on` directive is executed each time the associated event is triggered. + +The callback passed as reference receives [the event](https://developer.mozilla.org/en-US/docs/Web/API/Event) (`event`) and the returned value by this callback is ignored. + + +#### `wp-effect` + +It runs a callback **when the node is created and runs it again when the state or context changes**. + +You can attach several effects to the same DOM element by using the syntax `data-wp-effect--[unique-id]`. _The unique id doesn't need to be unique globally, it just needs to be different than the other unique ids of the `wp-effect` directives of that DOM element._ + +_Example of `wp-effect` directive_ +```html +
    +

    Counter:

    + + +
    +``` + +
    + See store used with the directive above + +```js +store( { + actions: { + increaseCounter: ({ context }) => { + context.counter++; + }, + decreaseCounter: ({ context }) => { + context.counter--; + }, + } + effects: { + logCounter: ({ context }) => console.log("Counter is " + context.counter + " at " + new Date() ), + }, +} ); +``` + +
    +
    + +The `wp-effect` directive is executed: + - when the element is created. + - each time that any of the properties of the `state` or `context` used inside the callback changes. + +The `wp-effect` directive can return a function. If it does, the returned function is used as cleanup logic, i.e., it will run just before the callback runs again, and it will run again when the element is removed from the DOM. + +As a reference, some use cases for this directive may be: +- logging. +- changing the title of the page. +- setting the focus on an element with `.focus()`. +- changing the state or context when certain conditions are met. + +#### `wp-init` + +It runs a callback **only when the node is created**. + +You can attach several `wp-init` to the same DOM element by using the syntax `data-wp-init--[unique-id]`. _The unique id doesn't need to be unique globally, it just needs to be different than the other unique ids of the `wp-init` directives of that DOM element._ + +_Example of `data-wp-init` directive_ +```html +
    +

    Hi! +

    +``` + +_Example of several `wp-init` directives on the same DOM element_ +```html +
    + +
    +``` + +
    + See store used with the directive above + +```js +store( { + effects: { + logTimeInit: () => console.log( `Init at ` + new Date() ), + focusFirstElement: ( { ref } ) => + ref.querySelector( 'input:first-child' ).focus(), + }, +} ); +``` + +
    +
    + + +The `wp-init` can return a function. If it does, the returned function will run when the element is removed from the DOM. + +### Values of directives are references to store properties + +The value assigned to a directive is a string pointing to a specific state, selector, action, or effect. *Using a Namespace is highly recommended* to define these elements of the store. + +In the following example we use the namespace `wpmovies` (plugin name is usually a good namespace name) to define the `isPlaying` selector. + +```js +store( { + selectors: { + wpmovies: { + isPlaying: ( { state } ) => state.wpmovies.currentVideo !== '', + }, + }, +} ); +``` + +And then, we use the string value `"selectors.wpmovies.isPlaying"` to assign the result of this selector to `data-bind--hidden`. + +```php +
    + +
    +``` + +These values assigned to directives are **references** to a particular property in the store. They are wired to the directives automatically so that each directive “knows” what store element (action, effect...) refers to without any additional configuration. + + +## The store + +The store is used to create the logic (actions, effects…) linked to the directives and the data used inside that logic (state, selectors…). + +**The store is usually created in the `view.js` file of each block**, although it can be initialized from the `render.php` of the block. + +The store contains the reactive state and the actions and effects that modify it. + +### Elements of the store + +#### State + +Defines data available to the HTML nodes of the page. It is important to differentiate between two ways to define the data: + - **Global state**: It is defined using the `store()` function, and the data is available to all the HTML nodes of the page. It can be accessed using the `state` property. + - **Context/Local State**: It is defined using the `data-wp-context` directive in an HTML node, and the data is available to that HTML node and its children. It can be accessed using the `context` property. + +```html +
    + + + + + + + +
    +``` + +```js +store( { + state: { + someText: "Hello Universe!" + } + actions: { + someAction: ({ state, context }) => { + state.someText // Access or modify global state - "Hello Universe!" + context.someText // Access or modify local state (context) - "Hello World!" + }, + }, +} ) +``` + +#### Actions + +Usually triggered by the `data-wp-on` directive (using event listeners) or other actions. + +#### Effects + +Automatically react to state changes. Usually triggered by `data-wp-effect` or `data-wp-init` directives. + +#### Selectors + +Also known as _derived state_, returns a computed version of the state. They can access both `state` and `context`. + +```js +// view.js +store( { + state: { + amount: 34, + defaultCurrency: 'EUR', + currencyExchange: { + USD: 1.1, + GBP: 0.85, + }, + }, + selectors: { + amountInUSD: ( { state } ) => + state.currencyExchange[ 'USD' ] * state.amount, + amountInGBP: ( { state } ) => + state.currencyExchange[ 'GBP' ] * state.amount, + }, +} ); +``` + +### Arguments passed to callbacks + +When a directive is evaluated, the reference callback receives an object with: + +- The **`store`** containing all the store properties, like `state`, `selectors`, `actions` or `effects` +- The **context** (an object containing the context defined in all the `wp-context` ancestors). +- The reference to the DOM element on which the directive was defined (a `ref`). +- Other properties relevant to the directive. For example, the `data-wp-on--click` directive also receives the instance of the [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) triggered by the user. + +_Example of action making use of all values received when it's triggered_ +```js +// view.js +store( { + state: { + theme: false, + }, + actions: { + toggle: ( { state, context, ref, event, className } ) => { + console.log( state ); + // `{ "theme": false }` + console.log( context ); + // `{ "isOpen": true }` + console.log( ref ); + // The DOM element + console.log( event ); + // The Event object if using the `data-wp-on` + console.log( className ); + // The class name if using the `data-wp-class` + }, + }, +} ); +``` + +This approach enables some functionalities that make directives flexible and powerful: + +- Actions and effects can read and modify the state and the context. +- Actions and state in blocks can be accessed by other blocks. +- Actions and effects can do anything a regular JavaScript function can do, like access the DOM or make API requests. +- Effects automatically react to state changes. + +### Setting the store + +#### On the client side + +*In the `view.js` file of each block* we can define both the state and the elements of the store referencing functions like actions, effects or selectors. + +`store` method used to set the store in javascript can be imported from `@wordpress/interactivity`. + +```js +// store +import { store } from '@wordpress/interactivity'; + +store( { + actions: { + toggle: ( { context } ) => { + context.isOpen = !context.isOpen; + }, + }, + effects: { + logIsOpen: ( { context } ) => { + // Log the value of `isOpen` each time it changes. + console.log( `Is open: ${ context.isOpen }` ); + } + }, +}); +``` + +#### On the server side + +The store can also be initialized on the server using the `wp_store()` function. You would typically do this in the `render.php` file of your block (the `render.php` templates were [introduced](https://make.wordpress.org/core/2022/10/12/block-api-changes-in-wordpress-6-1/) in WordPress 6.1). + +The store defined on the server with `wp_store()` gets merged with the stores defined in the view.js files. + +The `wp_store` function receives an [associative array](https://www.php.net/manual/en/language.types.array.php) as a parameter. + + +_Example of store initialized from the server with a `state` = `{ someValue: 123 }`_ + +```php +// render.php +wp_store( array( + 'state' => array( + 'myNamespace' => array( + 'someValue' = 123 + ) + ) +); +``` + +Initializing the store in the server also allows you to use any WordPress API. For example, you could use the Core Translation API to translate part of your state: + +```php +// render.php +wp_store( + array( + "state" => array( + "favoriteMovies" => array( + "1" => array( + "id" => "123-abc", + "movieName" => __("someMovieName", "textdomain") + ), + ), + ), + ) +); +``` + + + diff --git a/packages/interactivity/docs/assets/state-directives.png b/packages/interactivity/docs/assets/state-directives.png new file mode 100644 index 0000000000000000000000000000000000000000..feb93a2d1f8956ed5d16b59f02855bddccf027c5 GIT binary patch literal 339655 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%0Dkb?>#G;e79ODD-(^m8{_XRA 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

    h7uZ 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

    -h|{jCrmQ_ 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 literal 0 HcmV?d00001 From b340afd302c8fcea460c8e662386110e276e2e36 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 2 Aug 2023 14:49:19 +0300 Subject: [PATCH 034/150] Remove duplicated display URL in LinkControl (#53167) * Remove duplicated display URL in LinkControl * update block-editor-link-control__search-item-title line-height * update styles --- .../src/components/link-control/link-preview.js | 10 ++++++---- .../src/components/link-control/style.scss | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js index 71e3cd464ada14..1cf5c4a7b17115 100644 --- a/packages/block-editor/src/components/link-control/link-preview.js +++ b/packages/block-editor/src/components/link-control/link-preview.js @@ -42,11 +42,13 @@ export default function LinkPreview( { ( value && filterURLForDisplay( safeDecodeURI( value.url ), 16 ) ) || ''; - const displayTitle = richData?.title || value?.title || displayURL; - // url can be undefined if the href attribute is unset const isEmptyURL = ! value?.url?.length; + const displayTitle = + ! isEmptyURL && + stripHTML( richData?.title || value?.title || displayURL ); + let icon; if ( richData?.icon ) { @@ -87,10 +89,10 @@ export default function LinkPreview( { className="block-editor-link-control__search-item-title" href={ value.url } > - { stripHTML( displayTitle ) } + { displayTitle } - { value?.url && ( + { value?.url && displayTitle !== displayURL && ( { displayURL } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 8c398cb4fcdb07..5fd24e5e810d6d 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -215,9 +215,9 @@ $preview-image-height: 140px; .block-editor-link-control__search-item-title { display: block; - margin-bottom: 0.2em; font-weight: 500; position: relative; + line-height: $grid-unit-30; mark { font-weight: 600; @@ -291,6 +291,7 @@ $preview-image-height: 140px; display: flex; flex-direction: row; width: 100%; // clip. + align-items: center; } .block-editor-link-control__search-item-bottom { From 67154dcb47572c3b5860ef8d393c13e95feb390f Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 2 Aug 2023 14:49:36 +0300 Subject: [PATCH 035/150] Add context to the "Reset template" "Delete template" and "Edit template" commands (#52989) * Add context to the "Reset template" and "Edit template" commands * update delete labels and change to emdash * update to use `:` and rtl icon --- .../hooks/commands/use-edit-mode-commands.js | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js index 49b843a2d69f70..04abb70f6849d8 100644 --- a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js +++ b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js @@ -2,10 +2,11 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { __, isRTL } from '@wordpress/i18n'; +import { __, sprintf, isRTL } from '@wordpress/i18n'; import { trash, - backup, + rotateLeft, + rotateRight, layout, page, drawerLeft, @@ -16,6 +17,7 @@ import { keyboard, } from '@wordpress/icons'; import { useCommandLoader } from '@wordpress/commands'; +import { decodeEntities } from '@wordpress/html-entities'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as interfaceStore } from '@wordpress/interface'; @@ -35,6 +37,7 @@ import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); function usePageContentFocusCommands() { + const { record: template } = useEditedEntityRecord(); const { isPage, canvasMode, hasPageContentFocus } = useSelect( ( select ) => ( { isPage: select( editSiteStore ).isPage(), @@ -54,7 +57,11 @@ function usePageContentFocusCommands() { if ( hasPageContentFocus ) { commands.push( { name: 'core/switch-to-template-focus', - label: __( 'Edit template' ), + /* translators: %1$s: template title */ + label: sprintf( + 'Edit template: %s', + decodeEntities( template.title ) + ), icon: layout, callback: ( { close } ) => { setHasPageContentFocus( false ); @@ -94,12 +101,20 @@ function useManipulateDocumentCommands() { if ( isTemplateRevertable( template ) && ! hasPageContentFocus ) { const label = template.type === 'wp_template' - ? __( 'Reset template' ) - : __( 'Reset template part' ); + ? /* translators: %1$s: template title */ + sprintf( + 'Reset template: %s', + decodeEntities( template.title ) + ) + : /* translators: %1$s: template part title */ + sprintf( + 'Reset template part: %s', + decodeEntities( template.title ) + ); commands.push( { name: 'core/reset-template', label, - icon: backup, + icon: isRTL() ? rotateRight : rotateLeft, callback: ( { close } ) => { revertTemplate( template ); close(); @@ -110,8 +125,16 @@ function useManipulateDocumentCommands() { if ( isTemplateRemovable( template ) && ! hasPageContentFocus ) { const label = template.type === 'wp_template' - ? __( 'Delete template' ) - : __( 'Delete template part' ); + ? /* translators: %1$s: template title */ + sprintf( + 'Delete template: %s', + decodeEntities( template.title ) + ) + : /* translators: %1$s: template part title */ + sprintf( + 'Delete template part: %s', + decodeEntities( template.title ) + ); const path = template.type === 'wp_template' ? '/wp_template' From b6a6497a1cc5a1d66dc9c897b00988286b47f9aa Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Wed, 2 Aug 2023 14:26:28 +0100 Subject: [PATCH 036/150] Link Control: persist advanced settings toggle state to preferences if available (#52799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Link Control: Create a preference for whether the advanced section is open * move the useSelect to the component that uses it * Supply preference setter via settings * Pass setter to Post Editor * Provide local state fallbacks in absence of preference store settings * Conditionalise display of settings drawer to “edit” mode only * Extract to constant to improve comprehension * Fix bug in preferences resolution * Improve comments * Add e2e scaffold * Fix e2e test to correctly assert on feature * Remove focused test * Reinstate original logic to hide settings when not required * Scaffold documentation * Revert providing prefs via settings * Refactor to use `core/block-editor` as preference scope * Update docs * Reinstate remaining original conditional * tentative fix for the e2e test * Try a different syntax for shiftAlt * another attempt to fix the e2e test --------- Co-authored-by: Dave Smith Co-authored-by: MaggieCabrera --- .../src/components/link-control/README.md | 15 +++- .../src/components/link-control/index.js | 49 +++++++++-- .../src/components/link-control/test/index.js | 3 +- test/e2e/specs/editor/blocks/links.spec.js | 88 +++++++++++++++++++ 4 files changed, 145 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index c3fc7262adb956..fef68318867e8a 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -15,6 +15,16 @@ The distinction between the two components is perhaps best summarized by the fol - `` - an input for presenting and managing selection behaviors associated with choosing a URL, optionally from a pool of available candidates. - `` - includes the features of ``, plus additional UI and behaviors to control how this URL applies to the concept of a "link". This includes link "settings" (eg: "opens in new tab", etc) and dynamic, "on the fly" link creation capabilities. +## Persistent "Advanced" (settings) toggle state + +By default the link "settings" are hidden and can be toggled open/closed by way of a button labelled `Advanced` in the UI. + +In some circumstances if may be desirable to persist the toggle state of this portion of the UI so that it remains in the last state triggered by user interaction. + +For example, once the user has toggled the UI to "open", then it may remain open across all links on the site until such time as the user toggles the UI back again. + +Consumers who which to take advantage of this functionality should ensure that their block editor environment utilizes the [`@wordpress/preferences`](packages/preferences/README.md) package. By default the `` component will attempt to persist the state of UI to a setting named `linkControlSettingsDrawer` with a scope of `core/block-editor`. If the preferences package is not available then local state is used and the setting will not be persisted. + ## Search Suggestions When creating links the `LinkControl` component will handle two kinds of input from users: @@ -69,9 +79,7 @@ An array of settings objects associated with a link (for example: a setting to d To disable settings, pass in an empty array. for example: ```jsx - + ``` ### onChange @@ -192,6 +200,7 @@ A `suggestion` should have the following shape: )} /> ``` + ### renderControlBottom - Type: `Function` diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 8900c6c68086d9..efd09e6472e8b8 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -12,6 +12,8 @@ import { useRef, useState, useEffect } from '@wordpress/element'; import { focus } from '@wordpress/dom'; import { ENTER } from '@wordpress/keycodes'; import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies @@ -101,6 +103,9 @@ import { DEFAULT_LINK_SETTINGS } from './constants'; const noop = () => {}; +const PREFERENCE_SCOPE = 'core/block-editor'; +const PREFERENCE_KEY = 'linkControlSettingsDrawer'; + /** * Renders a link control. A link control is a controlled input which maintains * a value associated with a link (HTML anchor element) and relevant settings @@ -133,6 +138,41 @@ function LinkControl( { withCreateSuggestion = true; } + const [ settingsOpen, setSettingsOpen ] = useState( false ); + + const { advancedSettingsPreference } = useSelect( ( select ) => { + const prefsStore = select( preferencesStore ); + + return { + advancedSettingsPreference: + prefsStore.get( PREFERENCE_SCOPE, PREFERENCE_KEY ) ?? false, + }; + }, [] ); + + const { set: setPreference } = useDispatch( preferencesStore ); + + /** + * Sets the open/closed state of the Advanced Settings Drawer, + * optionlly persisting the state to the user's preferences. + * + * Note that Block Editor components can be consumed by non-WordPress + * environments which may not have preferences setup. + * Therefore a local state is also used as a fallback. + * + * @param {boolean} prefVal the open/closed state of the Advanced Settings Drawer. + */ + const setSettingsOpenWithPreference = ( prefVal ) => { + if ( setPreference ) { + setPreference( PREFERENCE_SCOPE, PREFERENCE_KEY, prefVal ); + } + setSettingsOpen( prefVal ); + }; + + // Block Editor components can be consumed by non-WordPress environments + // which may not have these preferences setup. + // Therefore a local state is used as a fallback. + const isSettingsOpen = advancedSettingsPreference || settingsOpen; + const isMounting = useRef( true ); const wrapperNode = useRef(); const textInputRef = useRef(); @@ -140,8 +180,6 @@ function LinkControl( { const settingsKeys = settings.map( ( { id } ) => id ); - const [ settingsOpen, setSettingsOpen ] = useState( false ); - const [ internalControlValue, setInternalControlValue, @@ -207,7 +245,6 @@ function LinkControl( { wrapperNode.current.ownerDocument.activeElement ); - setSettingsOpen( false ); setIsEditingLink( false ); }; @@ -292,7 +329,6 @@ function LinkControl( { const shownUnlinkControl = onRemove && value && ! isEditingLink && ! isCreatingPage; - const showSettings = !! settings?.length && isEditingLink && hasLinkValue; const showActions = isEditingLink && hasLinkValue; // Only show text control once a URL value has been committed @@ -302,6 +338,7 @@ function LinkControl( { const isEditing = ( isEditingLink || ! value ) && ! isCreatingPage; const isDisabled = ! valueHasChanges || currentInputIsEmpty; + const showSettings = !! settings?.length && isEditingLink && hasLinkValue; return (

    { ! currentInputIsEmpty && ( { } ); describe( 'Addition Settings UI', () => { - it( 'should not show a means to toggle the link settings when not editing a link', async () => { + it( 'should hide advanced link settings when not editing a link', async () => { const selectedLink = fauxEntitySuggestions[ 0 ]; const LinkControlConsumer = () => { @@ -1749,6 +1749,7 @@ describe( 'Addition Settings UI', () => { expect( settingsToggle ).not.toBeInTheDocument(); } ); + it( 'should provides a means to toggle the link settings', async () => { const selectedLink = fauxEntitySuggestions[ 0 ]; diff --git a/test/e2e/specs/editor/blocks/links.spec.js b/test/e2e/specs/editor/blocks/links.spec.js index 8443a04d0417e6..b84f954566fd55 100644 --- a/test/e2e/specs/editor/blocks/links.spec.js +++ b/test/e2e/specs/editor/blocks/links.spec.js @@ -145,4 +145,92 @@ test.describe( 'Links', () => { }, ] ); } ); + + test( 'toggle state of advanced link settings is preserved across editing links', async ( { + page, + editor, + pageUtils, + } ) => { + // Create a block with some text. + await editor.insertBlock( { + name: 'core/paragraph', + } ); + await page.keyboard.type( 'This is Gutenberg WordPress' ); + + // Select "WordPress". + await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' ); + + // Create a link. + await pageUtils.pressKeys( 'primary+k' ); + await page.keyboard.type( 'w.org' ); + await page.keyboard.press( 'Enter' ); + + // Move to edge of text "Gutenberg". + await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' ); // If you just use Alt here it won't work on windows. + await pageUtils.pressKeys( 'ArrowLeft' ); + await pageUtils.pressKeys( 'ArrowLeft' ); + + // Select "Gutenberg". + await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' ); + + // Create a link. + await pageUtils.pressKeys( 'primary+k' ); + await page.keyboard.type( 'https://wordpress.org/plugins/gutenberg/' ); + await page.keyboard.press( 'Enter' ); + + // Move back into the link. + await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' ); + await pageUtils.pressKeys( 'primary+k' ); + + // Toggle the Advanced settings to be open. + // This should set the editor preference to persist this + // UI state. + await page + .getByRole( 'region', { + name: 'Editor content', + } ) + .getByRole( 'button', { + name: 'Advanced', + } ) + .click(); + + // Move focus out of Link UI and into Paragraph block. + await pageUtils.pressKeys( 'Escape' ); + + // Move caret back into the "WordPress" link to trigger + // the Link UI for that link. + await pageUtils.pressKeys( 'Alt+ArrowRight' ); + await pageUtils.pressKeys( 'ArrowRight' ); + await pageUtils.pressKeys( 'ArrowRight' ); + + // Switch Link UI to "edit" mode. + await page.getByRole( 'button', { name: 'Edit' } ).click(); + + // Check that the Advanced settings are still expanded/open + // and I can see the open in new tab checkbox. This verifies + // that the editor preference was persisted. + await expect( page.getByLabel( 'Open in new tab' ) ).toBeVisible(); + + // Toggle the Advanced settings back to being closed. + await page + .getByRole( 'region', { + name: 'Editor content', + } ) + .getByRole( 'button', { + name: 'Advanced', + } ) + .click(); + + // Move focus out of Link UI and into Paragraph block. + await pageUtils.pressKeys( 'Escape' ); + + // Move caret back into the "Gutenberg" link and open + // the Link UI for that link. + await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' ); + await pageUtils.pressKeys( 'primary+k' ); + + // Check that the Advanced settings are still closed. + // This verifies that the editor preference was persisted. + await expect( page.getByLabel( 'Open in new tab' ) ).not.toBeVisible(); + } ); } ); From 9a0355726b8689541b6968cf795fef8821c1f6e7 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 2 Aug 2023 18:22:14 +0400 Subject: [PATCH 037/150] Migrate 'iframed inline styles' e2e tests to Playwright (#53269) * Migrate 'iframed inline styles' e2e tests to Playwright * Remove old test file --- .../iframed-equeue-block-assets.test.js | 51 ------------------- .../iframed-equeue-block-assets.spec.js | 44 ++++++++++++++++ 2 files changed, 44 insertions(+), 51 deletions(-) delete mode 100644 packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js create mode 100644 test/e2e/specs/editor/plugins/iframed-equeue-block-assets.spec.js diff --git a/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js b/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js deleted file mode 100644 index c29af593abb124..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - canvas, - activateTheme, -} from '@wordpress/e2e-test-utils'; - -async function getComputedStyle( context, selector, property ) { - return await context.evaluate( - ( sel, prop ) => - window.getComputedStyle( document.querySelector( sel ) )[ prop ], - selector, - property - ); -} - -describe( 'iframed inline styles', () => { - beforeEach( async () => { - // Activate the empty theme (block based theme), which is iframed. - await activateTheme( 'emptytheme' ); - await activatePlugin( 'gutenberg-test-iframed-enqueue_block_assets' ); - await createNewPost(); - } ); - - afterEach( async () => { - await deactivatePlugin( 'gutenberg-test-iframed-enqueue_block_assets' ); - await activateTheme( 'twentytwentyone' ); - } ); - - it( 'should load styles added through enqueue_block_assets', async () => { - await page.waitForSelector( 'iframe[name="editor-canvas"]' ); - // Check stylesheet. - expect( - await getComputedStyle( canvas(), 'body', 'background-color' ) - ).toBe( 'rgb(33, 117, 155)' ); - // Check inline style. - expect( await getComputedStyle( canvas(), 'body', 'padding' ) ).toBe( - '20px' - ); - - expect( - await canvas().evaluate( () => ( { ...document.body.dataset } ) ) - ).toEqual( { - iframedEnqueueBlockAssetsL10n: 'Iframed Enqueue Block Assets!', - } ); - } ); -} ); diff --git a/test/e2e/specs/editor/plugins/iframed-equeue-block-assets.spec.js b/test/e2e/specs/editor/plugins/iframed-equeue-block-assets.spec.js new file mode 100644 index 00000000000000..391e1fdc17ec72 --- /dev/null +++ b/test/e2e/specs/editor/plugins/iframed-equeue-block-assets.spec.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'iframed inline styles', () => { + test.beforeAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.activateTheme( 'emptytheme' ), + requestUtils.activatePlugin( + 'gutenberg-test-iframed-enqueue-block-assets' + ), + ] ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.deactivatePlugin( + 'gutenberg-test-iframed-enqueue-block-assets' + ), + ] ); + } ); + + test( 'should load styles added through enqueue_block_assets', async ( { + editor, + } ) => { + const canvasBody = editor.canvas.locator( 'body' ); + + await expect( canvasBody ).toHaveCSS( + 'background-color', + 'rgb(33, 117, 155)' + ); + await expect( canvasBody ).toHaveCSS( 'padding', '20px' ); + await expect( canvasBody ).toHaveAttribute( + 'data-iframed-enqueue-block-assets-l10n', + 'Iframed Enqueue Block Assets!' + ); + } ); +} ); From 979ef332d8767faaa7c54981aa7efda3466bb36c Mon Sep 17 00:00:00 2001 From: Bart Kalisz Date: Wed, 2 Aug 2023 17:21:08 +0200 Subject: [PATCH 038/150] Improve the efficiency of the useDebouncedInput hook (#53263) --- .../inserter/hooks/use-debounced-input.js | 15 ++++++++------- .../edit-site/src/utils/use-debounced-input.js | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/inserter/hooks/use-debounced-input.js b/packages/block-editor/src/components/inserter/hooks/use-debounced-input.js index 55d0ce989293e5..26cd6c0da0e0a9 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-debounced-input.js +++ b/packages/block-editor/src/components/inserter/hooks/use-debounced-input.js @@ -6,12 +6,13 @@ import { useDebounce } from '@wordpress/compose'; export default function useDebouncedInput( defaultValue = '' ) { const [ input, setInput ] = useState( defaultValue ); - const [ debounced, setter ] = useState( defaultValue ); - const setDebounced = useDebounce( setter, 250 ); + const [ debouncedInput, setDebouncedState ] = useState( defaultValue ); + + const setDebouncedInput = useDebounce( setDebouncedState, 250 ); + useEffect( () => { - if ( debounced !== input ) { - setDebounced( input ); - } - }, [ debounced, input ] ); - return [ input, setInput, debounced ]; + setDebouncedInput( input ); + }, [ input ] ); + + return [ input, setInput, debouncedInput ]; } diff --git a/packages/edit-site/src/utils/use-debounced-input.js b/packages/edit-site/src/utils/use-debounced-input.js index 55d0ce989293e5..26cd6c0da0e0a9 100644 --- a/packages/edit-site/src/utils/use-debounced-input.js +++ b/packages/edit-site/src/utils/use-debounced-input.js @@ -6,12 +6,13 @@ import { useDebounce } from '@wordpress/compose'; export default function useDebouncedInput( defaultValue = '' ) { const [ input, setInput ] = useState( defaultValue ); - const [ debounced, setter ] = useState( defaultValue ); - const setDebounced = useDebounce( setter, 250 ); + const [ debouncedInput, setDebouncedState ] = useState( defaultValue ); + + const setDebouncedInput = useDebounce( setDebouncedState, 250 ); + useEffect( () => { - if ( debounced !== input ) { - setDebounced( input ); - } - }, [ debounced, input ] ); - return [ input, setInput, debounced ]; + setDebouncedInput( input ); + }, [ input ] ); + + return [ input, setInput, debouncedInput ]; } From 072a09dcea16d088e0f1bd49de884845d92ad777 Mon Sep 17 00:00:00 2001 From: Chad Chadbourne <13856531+chad1008@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:37:19 -0400 Subject: [PATCH 039/150] `TabPanel`: implement Ariakit internally (#52133) Co-authored-by: Lena Morita --- packages/components/CHANGELOG.md | 1 + packages/components/src/tab-panel/index.tsx | 205 ++++++++------- .../src/tab-panel/stories/index.tsx | 6 + .../components/src/tab-panel/test/index.tsx | 237 ++++++++++-------- packages/components/src/tab-panel/types.ts | 11 +- .../test/__snapshots__/index.js.snap | 7 +- .../preferences-modal/test/index.js | 8 +- 7 files changed, 266 insertions(+), 209 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9dfc79d11f757f..c39a492f840a27 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)). - `MenuItemsChoice`, `MenuItem`: Support a `disabled` prop on a menu item ([#52737](https://github.com/WordPress/gutenberg/pull/52737)). +- `TabPanel`: Introduce a new version of `TabPanel` with updated internals and improved adherence to ARIA guidance on `tabpanel` focus behavior while maintaining the same functionality and API surface.([#52133](https://github.com/WordPress/gutenberg/pull/52133)). ### Bug Fix diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index 20063b2315dd39..4fff7dc306b023 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import * as Ariakit from '@ariakit/react'; import classnames from 'classnames'; import type { ForwardedRef } from 'react'; @@ -9,38 +10,30 @@ import type { ForwardedRef } from 'react'; */ import { forwardRef, - useState, useEffect, useLayoutEffect, useCallback, } from '@wordpress/element'; -import { useInstanceId } from '@wordpress/compose'; +import { useInstanceId, usePrevious } from '@wordpress/compose'; /** * Internal dependencies */ -import { NavigableMenu } from '../navigable-container'; + import Button from '../button'; -import type { TabButtonProps, TabPanelProps } from './types'; +import type { TabPanelProps } from './types'; import type { WordPressComponentProps } from '../ui/context'; -const TabButton = ( { - tabId, - children, - selected, - ...rest -}: TabButtonProps ) => ( - -); +// Separate the actual tab name from the instance ID. This is +// necessary because Ariakit internally uses the element ID when +// a new tab is selected, but our implementation looks specifically +// for the tab name to be passed to the `onSelect` callback. +const extractTabName = ( id: string | undefined | null ) => { + if ( typeof id === 'undefined' || id === null ) { + return; + } + return id.match( /^tab-panel-[0-9]*-(.*)/ )?.[ 1 ]; +}; /** * TabPanel is an ARIA-compliant tabpanel. @@ -92,26 +85,65 @@ const UnforwardedTabPanel = ( ref: ForwardedRef< any > ) => { const instanceId = useInstanceId( TabPanel, 'tab-panel' ); - const [ selected, setSelected ] = useState< string >(); - const handleTabSelection = useCallback( - ( tabKey: string ) => { - setSelected( tabKey ); - onSelect?.( tabKey ); + const prependInstanceId = useCallback( + ( tabName: string | undefined ) => { + if ( typeof tabName === 'undefined' ) { + return; + } + return `${ instanceId }-${ tabName }`; + }, + [ instanceId ] + ); + + const tabStore = Ariakit.useTabStore( { + setSelectedId: ( newTabValue ) => { + if ( typeof newTabValue === 'undefined' || newTabValue === null ) { + return; + } + + const newTab = tabs.find( + ( t ) => prependInstanceId( t.name ) === newTabValue + ); + if ( newTab?.disabled || newTab === selectedTab ) { + return; + } + + const simplifiedTabName = extractTabName( newTabValue ); + if ( typeof simplifiedTabName === 'undefined' ) { + return; + } + + onSelect?.( simplifiedTabName ); + }, + orientation, + selectOnMove, + defaultSelectedId: prependInstanceId( initialTabName ), + } ); + + const selectedTabName = extractTabName( tabStore.useState( 'selectedId' ) ); + + const setTabStoreSelectedId = useCallback( + ( tabName: string ) => { + tabStore.setState( 'selectedId', prependInstanceId( tabName ) ); }, - [ onSelect ] + [ prependInstanceId, tabStore ] ); - // Simulate a click on the newly focused tab, which causes the component - // to show the `tab-panel` associated with the clicked tab. - const activateTabAutomatically = ( - _childIndex: number, - child: HTMLElement - ) => { - child.click(); - }; - const selectedTab = tabs.find( ( { name } ) => name === selected ); - const selectedId = `${ instanceId }-${ selectedTab?.name ?? 'none' }`; + const selectedTab = tabs.find( ( { name } ) => name === selectedTabName ); + + const previousSelectedTabName = usePrevious( selectedTabName ); + + // Ensure `onSelect` is called when the initial tab is selected. + useEffect( () => { + if ( + previousSelectedTabName !== selectedTabName && + selectedTabName === initialTabName && + !! selectedTabName + ) { + onSelect?.( selectedTabName ); + } + }, [ selectedTabName, initialTabName, onSelect, previousSelectedTabName ] ); // Handle selecting the initial tab. useLayoutEffect( () => { @@ -119,25 +151,31 @@ const UnforwardedTabPanel = ( if ( selectedTab ) { return; } - const initialTab = tabs.find( ( tab ) => tab.name === initialTabName ); - // Wait for the denoted initial tab to be declared before making a // selection. This ensures that if a tab is declared lazily it can // still receive initial selection. if ( initialTabName && ! initialTab ) { return; } - if ( initialTab && ! initialTab.disabled ) { // Select the initial tab if it's not disabled. - handleTabSelection( initialTab.name ); + setTabStoreSelectedId( initialTab.name ); } else { - // Fallback to the first enabled tab when the initial is disabled. + // Fallback to the first enabled tab when the initial tab is + // disabled or it can't be found. const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled ); - if ( firstEnabledTab ) handleTabSelection( firstEnabledTab.name ); + if ( firstEnabledTab ) { + setTabStoreSelectedId( firstEnabledTab.name ); + } } - }, [ tabs, selectedTab, initialTabName, handleTabSelection ] ); + }, [ + tabs, + selectedTab, + initialTabName, + instanceId, + setTabStoreSelectedId, + ] ); // Handle the currently selected tab becoming disabled. useEffect( () => { @@ -145,59 +183,58 @@ const UnforwardedTabPanel = ( if ( ! selectedTab?.disabled ) { return; } - const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled ); - // If the currently selected tab becomes disabled, select the first enabled tab. // (if there is one). if ( firstEnabledTab ) { - handleTabSelection( firstEnabledTab.name ); + setTabStoreSelectedId( firstEnabledTab.name ); } - }, [ tabs, selectedTab?.disabled, handleTabSelection ] ); - + }, [ tabs, selectedTab?.disabled, setTabStoreSelectedId, instanceId ] ); return (
    - - { tabs.map( ( tab ) => ( - { + return ( + } - ) } - tabId={ `${ instanceId }-${ tab.name }` } - aria-controls={ `${ instanceId }-${ tab.name }-view` } - selected={ tab.name === selected } - key={ tab.name } - onClick={ () => handleTabSelection( tab.name ) } - disabled={ tab.disabled } - label={ tab.icon && tab.title } - icon={ tab.icon } - showTooltip={ !! tab.icon } - > - { ! tab.icon && tab.title } - - ) ) } - + > + { ! tab.icon && tab.title } + + ); + } ) } + { selectedTab && ( -
    { children( selectedTab ) } -
    + ) }
    ); diff --git a/packages/components/src/tab-panel/stories/index.tsx b/packages/components/src/tab-panel/stories/index.tsx index 07c076e1ce621a..b6a247d99ebdfb 100644 --- a/packages/components/src/tab-panel/stories/index.tsx +++ b/packages/components/src/tab-panel/stories/index.tsx @@ -96,3 +96,9 @@ WithTabIconsAndTooltips.args = { }, ], }; + +export const ManualActivation = Template.bind( {} ); +ManualActivation.args = { + ...Default.args, + selectOnMove: false, +}; diff --git a/packages/components/src/tab-panel/test/index.tsx b/packages/components/src/tab-panel/test/index.tsx index 16f88ee8a41e98..c3102fe26833bf 100644 --- a/packages/components/src/tab-panel/test/index.tsx +++ b/packages/components/src/tab-panel/test/index.tsx @@ -34,7 +34,8 @@ const TABS = [ }, ]; -const getSelectedTab = () => screen.getByRole( 'tab', { selected: true } ); +const getSelectedTab = async () => + await screen.findByRole( 'tab', { selected: true } ); let originalGetClientRects: () => DOMRectList; @@ -62,7 +63,7 @@ describe.each( [ } ); describe( 'Accessibility and semantics', () => { - test( 'should use the correct aria attributes', () => { + it( 'should use the correct aria attributes', async () => { const panelRenderFunction = jest.fn(); render( @@ -71,7 +72,7 @@ describe.each( [ const tabList = screen.getByRole( 'tablist' ); const allTabs = screen.getAllByRole( 'tab' ); - const selectedTabPanel = screen.getByRole( 'tabpanel' ); + const selectedTabPanel = await screen.findByRole( 'tabpanel' ); expect( tabList ).toBeVisible(); expect( tabList ).toHaveAttribute( @@ -94,7 +95,7 @@ describe.each( [ ); } ); - test( 'should display a tooltip when hovering tabs provided with an icon', async () => { + it( 'should display a tooltip when hovering tabs provided with an icon', async () => { const user = userEvent.setup(); const panelRenderFunction = jest.fn(); @@ -138,7 +139,7 @@ describe.each( [ } } ); - test( 'should display a tooltip when moving the selection via the keyboard on tabs provided with an icon', async () => { + it( 'should display a tooltip when moving the selection via the keyboard on tabs provided with an icon', async () => { const user = userEvent.setup(); const mockOnSelect = jest.fn(); @@ -165,10 +166,10 @@ describe.each( [ ); - expect( getSelectedTab() ).not.toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).not.toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - await expect( getSelectedTab() ).not.toHaveFocus(); + expect( await getSelectedTab() ).not.toHaveFocus(); // Tab to focus the tablist. Make sure alpha is focused, and that the // corresponding tooltip is shown. @@ -176,7 +177,7 @@ describe.each( [ await user.keyboard( '[Tab]' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( screen.getByText( 'Alpha' ) ).toBeInTheDocument(); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); // Move selection with arrow keys. Make sure beta is focused, and that // the corresponding tooltip is shown. @@ -185,7 +186,7 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); expect( screen.getByText( 'Beta' ) ).toBeInTheDocument(); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); // Move selection with arrow keys. Make sure gamma is focused, and that // the corresponding tooltip is shown. @@ -194,7 +195,7 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); expect( screen.getByText( 'Gamma' ) ).toBeInTheDocument(); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); // Move selection with arrow keys. Make sure beta is focused, and that // the corresponding tooltip is shown. @@ -203,7 +204,7 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); expect( screen.getByText( 'Beta' ) ).toBeInTheDocument(); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); } ); } ); @@ -215,11 +216,10 @@ describe.each( [ ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( - screen.getByRole( 'tabpanel', { name: 'Alpha' } ) + await screen.findByRole( 'tabpanel', { name: 'Alpha' } ) ).toBeInTheDocument(); - expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] ); } ); it( 'should fall back to first enabled tab if the active tab is removed', async () => { @@ -239,12 +239,12 @@ describe.each( [ onSelect={ mockOnSelect } /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); } ); } ); describe( 'With `initialTabName`', () => { - it( 'should render the tab set by initialTabName prop', () => { + it( 'should render the tab set by initialTabName prop', async () => { render( ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); } ); it( 'should not select a tab when `initialTabName` does not match any known tab', () => { @@ -273,8 +273,7 @@ describe.each( [ // No tabpanel should be rendered either expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument(); } ); - - it( 'should not change tabs when initialTabName is changed', () => { + it( 'should not change tabs when initialTabName is changed', async () => { const { rerender } = render( ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); } ); it( 'should fall back to the tab associated to `initialTabName` if the currently active tab is removed', async () => { @@ -307,13 +306,11 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); rerender( @@ -325,12 +322,11 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); } ); - it( 'should have no active tabs when the tab associated to `initialTabName` is removed while being the active tab', () => { + it( 'should have no active tabs when the tab associated to `initialTabName` is removed while being the active tab', async () => { const mockOnSelect = jest.fn(); const { rerender } = render( @@ -342,7 +338,7 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); @@ -362,7 +358,7 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); } ); - it( 'waits for the tab with the `initialTabName` to be present in the `tabs` array before selecting it', () => { + it( 'waits for the tab with the `initialTabName` to be present in the `tabs` array before selecting it', async () => { const mockOnSelect = jest.fn(); const { rerender } = render( ); - expect( getSelectedTab() ).toHaveTextContent( 'Delta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Delta' ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'delta' ); } ); } ); @@ -433,7 +429,7 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); } ); - it( 'should select first enabled tab when the initial tab is disabled', () => { + it( 'should select first enabled tab when the initial tab is disabled', async () => { const mockOnSelect = jest.fn(); const { rerender } = render( @@ -452,7 +448,7 @@ describe.each( [ // As alpha (first tab) is disabled, // the first enabled tab should be gamma. - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); // Re-enable all tabs rerender( @@ -465,10 +461,10 @@ describe.each( [ // Even if the initial tab becomes enabled again, the selected tab doesn't // change. - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); } ); - it( 'should select first enabled tab when the tab associated to `initialTabName` is disabled', () => { + it( 'should select first enabled tab when the tab associated to `initialTabName` is disabled', async () => { const mockOnSelect = jest.fn(); const { rerender } = render( @@ -487,7 +483,7 @@ describe.each( [ // As alpha (first tab), and beta (the initial tab), are both // disabled the first enabled tab should be gamma. - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); // Re-enable all tabs rerender( @@ -501,10 +497,10 @@ describe.each( [ // Even if the initial tab becomes enabled again, the selected tab doesn't // change. - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); } ); - it( 'should select the first enabled tab when the selected tab becomes disabled', () => { + it( 'should select the first enabled tab when the selected tab becomes disabled', async () => { const mockOnSelect = jest.fn(); const { rerender } = render( ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); @@ -531,7 +527,7 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); @@ -543,12 +539,12 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); } ); - it( 'should select the first enabled tab when the tab associated to `initialTabName` becomes disabled while being the active tab', () => { + it( 'should select the first enabled tab when the tab associated to `initialTabName` becomes disabled while being the active tab', async () => { const mockOnSelect = jest.fn(); const { rerender } = render( @@ -560,7 +556,7 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); @@ -577,7 +573,7 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); @@ -590,7 +586,7 @@ describe.each( [ /> ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); } ); } ); @@ -610,31 +606,28 @@ describe.each( [ ); // Alpha is the initially selected tab - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( - screen.getByRole( 'tabpanel', { name: 'Alpha' } ) + await screen.findByRole( 'tabpanel', { name: 'Alpha' } ) ).toBeInTheDocument(); - expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); // Click on Beta, make sure beta is the selected tab await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); expect( screen.getByRole( 'tabpanel', { name: 'Beta' } ) ).toBeInTheDocument(); - expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 1 ] ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); // Click on Alpha, make sure beta is the selected tab await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( screen.getByRole( 'tabpanel', { name: 'Alpha' } ) ).toBeInTheDocument(); - expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); @@ -654,24 +647,24 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); // Tab to focus the tablist. Make sure alpha is focused. - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).not.toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).not.toHaveFocus(); await user.keyboard( '[Tab]' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); // Navigate forward with arrow keys and make sure the Beta tab is // selected automatically. await user.keyboard( '[ArrowRight]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); // Navigate backwards with arrow keys. Make sure alpha is // selected automatically. await user.keyboard( '[ArrowLeft]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); @@ -692,24 +685,24 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); // Tab to focus the tablist. Make sure Alpha is focused. - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).not.toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).not.toHaveFocus(); await user.keyboard( '[Tab]' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); // Navigate backwards with arrow keys and make sure that the Gamma tab // (the last tab) is selected automatically. await user.keyboard( '[ArrowLeft]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); // Navigate forward with arrow keys. Make sure alpha (the first tab) is // selected automatically. await user.keyboard( '[ArrowRight]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); @@ -730,22 +723,22 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); // Tab to focus the tablist. Make sure alpha is focused. - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).not.toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).not.toHaveFocus(); await user.keyboard( '[Tab]' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); // Press the arrow up key, nothing happens. await user.keyboard( '[ArrowUp]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); // Press the arrow down key, nothing happens await user.keyboard( '[ArrowDown]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); @@ -766,38 +759,38 @@ describe.each( [ ); // Make sure alpha is still focused. - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); // Navigate forward with arrow keys and make sure the Beta tab is // selected automatically. await user.keyboard( '[ArrowDown]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); // Navigate backwards with arrow keys. Make sure alpha is // selected automatically. await user.keyboard( '[ArrowUp]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); // Navigate backwards with arrow keys. Make sure alpha is // selected automatically. await user.keyboard( '[ArrowUp]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); // Navigate backwards with arrow keys. Make sure alpha is // selected automatically. await user.keyboard( '[ArrowDown]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 5 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); @@ -826,10 +819,10 @@ describe.each( [ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); // Tab to focus the tablist. Make sure Alpha is focused. - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - await expect( getSelectedTab() ).not.toHaveFocus(); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).not.toHaveFocus(); await user.keyboard( '[Tab]' ); - await expect( getSelectedTab() ).toHaveFocus(); + expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); // Press the right arrow key three times. Since the delta tab is disabled: @@ -838,26 +831,48 @@ describe.each( [ // `mockOnSelect` function gets called only twice (and not three times) // - it will receive focus, when using arrow keys await user.keyboard( '[ArrowRight][ArrowRight][ArrowRight]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - await expect( + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( screen.getByRole( 'tab', { name: 'Delta' } ) ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); // Navigate backwards with arrow keys. The gamma tab receives focus. + // The `mockOnSelect` callback doesn't fire, since the gamma tab was + // already selected. await user.keyboard( '[ArrowLeft]' ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - await expect( getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveFocus(); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); - // Click on on the disabled tab. Compared to using arrow keys to move the + // Click on the disabled tab. Compared to using arrow keys to move the // focus, disabled tabs ignore pointer clicks — and therefore, they don't // receive focus, nor they cause the `mockOnSelect` function to fire. await user.click( screen.getByRole( 'tab', { name: 'Delta' } ) ); - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - await expect( getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); + expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( await getSelectedTab() ).toHaveFocus(); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + } ); + + it( 'should not focus the next tab when the Tab key is pressed', async () => { + const user = userEvent.setup(); + + render( undefined } /> ); + + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await user.keyboard( '[Tab]' ); + expect( + await screen.findByRole( 'tab', { name: 'Alpha' } ) + ).toHaveFocus(); + + // Because all other tabs should have `tabindex=-1`, pressing Tab + // should NOT move the focus to the next tab, which is Beta. + await user.keyboard( '[Tab]' ); + expect( + await screen.findByRole( 'tab', { name: 'Beta' } ) + ).not.toHaveFocus(); } ); it( 'switches to manual tab activation when the `selectOnMove` prop is set to `false`', async () => { @@ -873,47 +888,51 @@ describe.each( [ /> ); - // onSelect gets called on the initial render. + // onSelect gets called on the initial render with the default + // selected tab. expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); // Click on Alpha and make sure it is selected. + // onSelect shouldn't fire since the selected tab didn't change. await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); // Navigate forward with arrow keys. Make sure Beta is focused, but // that the tab selection happens only when pressing the spacebar // or enter key. await user.keyboard( '[ArrowRight]' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); - expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveFocus(); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( + await screen.findByRole( 'tab', { name: 'Beta' } ) + ).toHaveFocus(); await user.keyboard( '[Enter]' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); // Navigate forward with arrow keys. Make sure Gamma (last tab) is // focused, but that tab selection happens only when pressing the // spacebar or enter key. await user.keyboard( '[ArrowRight]' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( screen.getByRole( 'tab', { name: 'Gamma' } ) ).toHaveFocus(); await user.keyboard( '[Space]' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); } ); } ); describe( 'Tab Attributes', () => { - it( "should apply the tab's `className` to the tab button", () => { + it( "should apply the tab's `className` to the tab button", async () => { render( undefined } /> ); - expect( screen.getByRole( 'tab', { name: 'Alpha' } ) ).toHaveClass( - 'alpha-class' - ); + expect( + await screen.findByRole( 'tab', { name: 'Alpha' } ) + ).toHaveClass( 'alpha-class' ); expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass( 'beta-class' ); @@ -935,8 +954,8 @@ describe.each( [ ); // Make sure that only the selected tab has the active class - expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( getSelectedTab() ).toHaveClass( activeClass ); + expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( await getSelectedTab() ).toHaveClass( activeClass ); screen .getAllByRole( 'tab', { selected: false } ) .forEach( ( unselectedTab ) => { @@ -947,8 +966,8 @@ describe.each( [ await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) ); // Make sure that only the selected tab has the active class - expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( getSelectedTab() ).toHaveClass( activeClass ); + expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( await getSelectedTab() ).toHaveClass( activeClass ); screen .getAllByRole( 'tab', { selected: false } ) .forEach( ( unselectedTab ) => { diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts index 4bef866923ebca..1f4dc7c677483a 100644 --- a/packages/components/src/tab-panel/types.ts +++ b/packages/components/src/tab-panel/types.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { MouseEvent, ReactNode } from 'react'; +import type { ReactNode } from 'react'; /** * Internal dependencies @@ -31,15 +31,6 @@ type Tab = { disabled?: boolean; } & Record< any, any >; -export type TabButtonProps = { - children: ReactNode; - label?: string; - onClick: ( event: MouseEvent ) => void; - selected: boolean; - showTooltip?: boolean; - tabId: string; -} & Pick< Tab, 'className' | 'icon' | 'disabled' >; - export type TabPanelProps = { /** * The class name to add to the active tab. diff --git a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap index 32254bb91c6955..bcaed355d48adf 100644 --- a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap @@ -115,6 +115,8 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active aria-controls="tab-panel-0-general-view" aria-selected="true" class="components-button components-tab-panel__tabs-item is-active" + data-active-item="" + data-command="" id="tab-panel-0-general" role="tab" type="button" @@ -125,9 +127,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active aria-controls="tab-panel-0-blocks-view" aria-selected="false" class="components-button components-tab-panel__tabs-item" + data-command="" id="tab-panel-0-blocks" role="tab" - tabindex="-1" type="button" > Blocks @@ -136,9 +138,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active aria-controls="tab-panel-0-panels-view" aria-selected="false" class="components-button components-tab-panel__tabs-item" + data-command="" id="tab-panel-0-panels" role="tab" - tabindex="-1" type="button" > Panels @@ -149,6 +151,7 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active class="components-tab-panel__tab-content" id="tab-panel-0-general-view" role="tabpanel" + tabindex="0" >
    jest.fn() ); describe( 'EditPostPreferencesModal', () => { describe( 'should match snapshot when the modal is active', () => { - it( 'large viewports', () => { + it( 'large viewports', async () => { useSelect.mockImplementation( () => [ true, true, false ] ); useViewportMatch.mockImplementation( () => true ); render( ); expect( - screen.getByRole( 'dialog', { name: 'Preferences' } ) + await screen.findByRole( 'dialog', { name: 'Preferences' } ) ).toMatchSnapshot(); } ); - it( 'small viewports', () => { + it( 'small viewports', async () => { useSelect.mockImplementation( () => [ true, true, false ] ); useViewportMatch.mockImplementation( () => false ); render( ); expect( - screen.getByRole( 'dialog', { name: 'Preferences' } ) + await screen.findByRole( 'dialog', { name: 'Preferences' } ) ).toMatchSnapshot(); } ); } ); From a00bf4371c1349548cea07aa8bf02661e09ed8b9 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 2 Aug 2023 16:29:12 +0000 Subject: [PATCH 040/150] Bump plugin version to 16.4.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index b00ddcb03a4b65..92fff2a50fd94f 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.1 * Requires PHP: 7.0 - * Version: 16.3.0 + * Version: 16.4.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 4cf9a362b07da9..426e78c137db6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "16.3.0", + "version": "16.4.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 468a80fd904d41..4b24454c247907 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "16.3.0", + "version": "16.4.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From ed63cbe8e65b95c5105205af59a93a22253aa499 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 2 Aug 2023 18:45:36 +0200 Subject: [PATCH 041/150] Mobile - KeyboardAwareFlatList - Set scrollEnabled to false for the FlatList component to avoid an error message, scroll is not actually needed within the FlatList since the ScrollView is the component that handles it (#53237) --- .../components/src/mobile/keyboard-aware-flat-list/index.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 90fda81d05b2f6..8b233612763814 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 @@ -154,7 +154,7 @@ export const KeyboardAwareFlatList = ( { scrollEventThrottle={ 16 } style={ style } > - + ); }; From 926087ba9107cadb343ee85075b3c4f8ef318520 Mon Sep 17 00:00:00 2001 From: James Koster Date: Wed, 2 Aug 2023 18:00:00 +0100 Subject: [PATCH 042/150] Fix document title alignment in command palette button (#53224) --- .../header-edit-mode/document-actions/style.scss | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss b/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss index cfaed598304055..02bc40f4259f52 100644 --- a/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss +++ b/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss @@ -17,6 +17,10 @@ width: min(100%, 380px); } + &:hover { + background-color: $gray-200; + } + .components-button { border-radius: $grid-unit-05; @@ -26,10 +30,6 @@ } } - @include break-medium() { - width: 50%; - } - @include break-large() { width: min(100%, 450px); } @@ -54,6 +54,9 @@ flex-grow: 1; overflow: hidden; + // Offset the layout based on the width of the ⌘K label. This ensures the title is centrally aligned. + padding-left: $grid-unit-40; + &:hover { color: var(--wp-block-synced-color); } @@ -67,6 +70,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + max-width: 50%; } .edit-site-document-actions.is-page & { @@ -90,6 +94,7 @@ .edit-site-document-actions__shortcut { color: $gray-800; + min-width: $grid-unit-40; } .edit-site-document-actions__back.components-button.has-icon.has-text { @@ -98,9 +103,11 @@ color: $gray-700; gap: 0; z-index: 1; + position: absolute; &:hover { color: currentColor; + background-color: transparent; } .edit-site-document-actions.is-animated & { From d8c905a4bde9c2a85adb60afc45a14d385405a48 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 2 Aug 2023 19:09:46 +0200 Subject: [PATCH 043/150] [RNMobile] Avoid navigation container dismissing the keyboard (#53270) * Avoid navigation container dismissing keyboard * Move `keyboardHandlingEnabled` to screen options --- .../bottom-sheet-navigation/navigation-container.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js index 3fe7ec003ceb8b..5f87ff80194b81 100644 --- a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js +++ b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js @@ -59,6 +59,7 @@ const options = { headerShown: false, gestureEnabled: false, cardStyleInterpolator: fadeConfig, + keyboardHandlingEnabled: false, }; const HEIGHT_ANIMATION_DURATION = 300; From fb428fc726511c04cb69b22a8ef95c87cf87b132 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 2 Aug 2023 17:18:54 +0000 Subject: [PATCH 044/150] Update Changelog for 16.4.0-rc.1 --- changelog.txt | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/changelog.txt b/changelog.txt index ff4942d8e06a1e..57c4e955520ab0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,317 @@ == Changelog == += 16.4.0-rc.1 = + +## Changelog + +### Features + +#### Components +- Introduce a basic ProgressBar component. ([53030](https://github.com/WordPress/gutenberg/pull/53030)) + +#### Block Editor +- Link Control: Persist advanced settings toggle state to preferences if available. ([52799](https://github.com/WordPress/gutenberg/pull/52799)) + +#### Block Library +- Behaviors: Extend Global Styles API to read/write behaviors config. ([52370](https://github.com/WordPress/gutenberg/pull/52370)) + +### Enhancements + +#### Site Editor +- Add context to the "Reset template" "Delete template" and "Edit template" commands. ([52989](https://github.com/WordPress/gutenberg/pull/52989)) +- Add `xhuge` breakpoint (1920px) and update pattern grid. ([52942](https://github.com/WordPress/gutenberg/pull/52942)) +- Display keyboard shortcut for command palette in site view. ([52841](https://github.com/WordPress/gutenberg/pull/52841)) +- Make sure only one Site updated notice displays at a time. ([53087](https://github.com/WordPress/gutenberg/pull/53087)) +- Pattern library: Switch to three column layout on `huge` screens. ([52927](https://github.com/WordPress/gutenberg/pull/52927)) +- Template Descriptions: Tidy up all abbreviations of 'example' to be e.g. ([52848](https://github.com/WordPress/gutenberg/pull/52848)) +- Try adding further details to template part panel. ([52476](https://github.com/WordPress/gutenberg/pull/52476)) +- Update the `ConfirmDialog` that appears when applying a style revision over unsaved changes. ([52972](https://github.com/WordPress/gutenberg/pull/52972)) +- Update: Improve titles of author templates in query title block. ([52732](https://github.com/WordPress/gutenberg/pull/52732)) + +#### Post Editor +- Add common commands (breadcrumbs, live preview, pre-publish checklist). ([53073](https://github.com/WordPress/gutenberg/pull/53073)) +- Use hooks instead of HoCs for: + - `DiscussionPanel`. ([53232](https://github.com/WordPress/gutenberg/pull/53232)) + - `PostPingbacks`. ([53228](https://github.com/WordPress/gutenberg/pull/53228)) + - `PostComments`. ([53231](https://github.com/WordPress/gutenberg/pull/53231)) + - `PostExcerpt`. ([53229](https://github.com/WordPress/gutenberg/pull/53229)) + - `PostTypeSupportCheck`. ([53235](https://github.com/WordPress/gutenberg/pull/53235)) + +#### Block Library +- Footnotes: Add link, background, and text color support. ([52897](https://github.com/WordPress/gutenberg/pull/52897)) +- Image block: Fix flaky tests. ([52442](https://github.com/WordPress/gutenberg/pull/52442)) +- Preformatted: Add spacing support. ([45196](https://github.com/WordPress/gutenberg/pull/45196)) +- Social Links: Add Threads Icon. ([52685](https://github.com/WordPress/gutenberg/pull/52685)) +- Verse: Enable the line breaks. ([52928](https://github.com/WordPress/gutenberg/pull/52928)) + +#### Patterns +- Align height of save button and patterns pagination. ([52764](https://github.com/WordPress/gutenberg/pull/52764)) +- Allow inserting of unsynced patterns from quick inserter. ([52866](https://github.com/WordPress/gutenberg/pull/52866)) +- Show the default patterns icons for all pattern blocks in inserter. ([53208](https://github.com/WordPress/gutenberg/pull/53208)) +- Update the Manage all my patterns command to redirect to site editor patterns list. ([52817](https://github.com/WordPress/gutenberg/pull/52817)) + +#### Interactivity API +- Use defer loading strategy for frontend view scripts. ([52536](https://github.com/WordPress/gutenberg/pull/52536)) +- [create-block] Refinements to the create-block-interactive-template package. ([52801](https://github.com/WordPress/gutenberg/pull/52801)) + +#### Components +- Settings: Show message when Visual or Code editor are disabled. ([52737](https://github.com/WordPress/gutenberg/pull/52737)) +- `TabPanel`: Implement Ariakit internally. ([52133](https://github.com/WordPress/gutenberg/pull/52133)) + +#### Global Styles +- Site editor: Conditionally render global styles revisions footer in sidebar. ([53204](https://github.com/WordPress/gutenberg/pull/53204)) + +#### Block Editor +- Remove duplicated display URL in LinkControl. ([53167](https://github.com/WordPress/gutenberg/pull/53167)) + +#### Synced Patterns +- Remove extraneous "Detach" ToolbarButton for synced patterns. ([53121](https://github.com/WordPress/gutenberg/pull/53121)) + +#### Icons +- Add keyboard icon and use in relative commands. ([53083](https://github.com/WordPress/gutenberg/pull/53083)) + +#### Plugin +- Bump plugin minimum supported PHP version to 7.0. ([52982](https://github.com/WordPress/gutenberg/pull/52982)) + +#### Data Layer +- Promisify action creator return type for WP data dispatch. ([52530](https://github.com/WordPress/gutenberg/pull/52530)) + + +### Bug Fixes + +#### Block Library +- Avatar: Fix global border styles generation. ([53007](https://github.com/WordPress/gutenberg/pull/53007)) +- Check if object exists before accessing its properties. ([52870](https://github.com/WordPress/gutenberg/pull/52870)) +- Cover block: Disable contrast checker. ([53080](https://github.com/WordPress/gutenberg/pull/53080)) +- Disambiguate "Import" button string. ([52907](https://github.com/WordPress/gutenberg/pull/52907)) +- Footnotes: + - Footnotes/RichText: Fix getRichTextValues for deeply nested blocks. ([53034](https://github.com/WordPress/gutenberg/pull/53034)) + - Disable based on post type. ([52934](https://github.com/WordPress/gutenberg/pull/52934)) + - Disable for synced patterns and prevent duplication for pages in site editor. ([53003](https://github.com/WordPress/gutenberg/pull/53003)) + - Fix published preview. ([53072](https://github.com/WordPress/gutenberg/pull/53072)) + - Store in revisions. ([52686](https://github.com/WordPress/gutenberg/pull/52686)) +- Image block: Fix image size at wide and full width. ([53184](https://github.com/WordPress/gutenberg/pull/53184)) +- Navigation Sidebar: Fetch the blocks from the content when trying to load navigations. ([52899](https://github.com/WordPress/gutenberg/pull/52899)) +- Navigation: Load the raw property on the navigation fallback. ([52758](https://github.com/WordPress/gutenberg/pull/52758)) +- Remove block tools back compat component schedule for deprecated in 6.3. ([53115](https://github.com/WordPress/gutenberg/pull/53115)) +- Verse: Disable line breaks. ([52783](https://github.com/WordPress/gutenberg/pull/52783)) +- Video: Fixing styles that vertical alignment of the video. ([53131](https://github.com/WordPress/gutenberg/pull/53131)) + +#### Site Editor +- Add navigation type to title labels map. ([53074](https://github.com/WordPress/gutenberg/pull/53074)) +- Command Palette: + - Add `Open styles revisions` command conditionally. ([52945](https://github.com/WordPress/gutenberg/pull/52945)) + - Command Palette: Remove double border on results pages. ([52873](https://github.com/WordPress/gutenberg/pull/52873)) + - CommandPalette: Fixed to not execute commands in IME composition. ([52844](https://github.com/WordPress/gutenberg/pull/52844)) + - Defer to preceding handlers in command palette keyboard shortcut. ([53001](https://github.com/WordPress/gutenberg/pull/53001)) + - [Core Commands]: Handle navigation commands based on access of site editor. ([52987](https://github.com/WordPress/gutenberg/pull/52987)) +- Fix block top toolbar artifact in navigation isolation. ([53110](https://github.com/WordPress/gutenberg/pull/53110)) +- Fix canvas mode sync with URL. ([52996](https://github.com/WordPress/gutenberg/pull/52996)) +- Fix the template parts link on the list page. ([52891](https://github.com/WordPress/gutenberg/pull/52891)) +- Open template parts from the list page in canvas view mode. ([52916](https://github.com/WordPress/gutenberg/pull/52916)) +- Fix the typo in the title label map. ([53071](https://github.com/WordPress/gutenberg/pull/53071)) +- Fix: Block toolbar obscuring document tools when Top Toolbar is enabled. ([52722](https://github.com/WordPress/gutenberg/pull/52722)) +- ResizableFrame: Account for window resizing. ([52697](https://github.com/WordPress/gutenberg/pull/52697)) +- Sidebar: Restore Back button 'go to parent' functionality. ([52910](https://github.com/WordPress/gutenberg/pull/52910)) +- Top toolbar: Fix issues with save button overlap and plugin buttons. ([53101](https://github.com/WordPress/gutenberg/pull/53101)) +- Update document title buttons radius. ([53221](https://github.com/WordPress/gutenberg/pull/53221)) + +#### Patterns +- Add id to pattern inserted notice to stop multiple notices stacking. ([52746](https://github.com/WordPress/gutenberg/pull/52746)) +- Allow orphaned template parts to appear in "general" category. ([52961](https://github.com/WordPress/gutenberg/pull/52961)) +- Correctly color code unsynced patterns titles in Site Editor. ([52958](https://github.com/WordPress/gutenberg/pull/52958)) +- Fix auto-size patterns triggering scrollbar flickering on certain size. ([52921](https://github.com/WordPress/gutenberg/pull/52921)) +- Fix color and behavior of unsynced patterns in block inserter when searching for `reusable`. ([53205](https://github.com/WordPress/gutenberg/pull/53205)) +- Fix editor crashing on certain search filter combinations. ([52956](https://github.com/WordPress/gutenberg/pull/52956)) +- Fix empty general template parts in Patterns. ([52747](https://github.com/WordPress/gutenberg/pull/52747)) +- Fix not expanding pattern in page editor. ([53169](https://github.com/WordPress/gutenberg/pull/53169)) +- Fix: Snack bar not fixed on certain pages in the Site Editor. ([53207](https://github.com/WordPress/gutenberg/pull/53207)) +- Pattern: Add getBlockRootClientId call. ([53206](https://github.com/WordPress/gutenberg/pull/53206)) +- Patterns Browse Screen: Fix back button when switching between categories. ([52964](https://github.com/WordPress/gutenberg/pull/52964)) +- Reset current page when search filters change. ([52933](https://github.com/WordPress/gutenberg/pull/52933)) +- Site Editor: Fix site link accessibility issues. ([52744](https://github.com/WordPress/gutenberg/pull/52744)) +- Site Editor: Use the correct icon for Patterns in sidebar card. ([52931](https://github.com/WordPress/gutenberg/pull/52931)) + +#### Post Editor +- Allow styles to be changed dynamically through editor settings. ([52767](https://github.com/WordPress/gutenberg/pull/52767)) +- Distraction Free: Fix conflict with `showListViewByDefault` preference. ([52914](https://github.com/WordPress/gutenberg/pull/52914)) +- Editor: Set default parameter for '__unstableSaveForPreview'. ([53079](https://github.com/WordPress/gutenberg/pull/53079)) +- Fix toolbar when previewing devices in post editor. ([52770](https://github.com/WordPress/gutenberg/pull/52770)) +- I18N: Add missing Gettext wrapper on strings in Edit Post overview sidebar. ([52971](https://github.com/WordPress/gutenberg/pull/52971)) +- shimAttributeSource: Don't run outside the registerBlockType filter. ([53015](https://github.com/WordPress/gutenberg/pull/53015)) + +#### Global Styles +- Global styles revisions: Display text if no revisions are found. ([52865](https://github.com/WordPress/gutenberg/pull/52865)) +- Spacing presets: Fix bug with select control adding undefined preset values. ([53005](https://github.com/WordPress/gutenberg/pull/53005)) +- Style Engine: Switch off optimize by default. ([53085](https://github.com/WordPress/gutenberg/pull/53085)) + +#### Block Editor +- Fix spacing for LinkControl actions. ([53055](https://github.com/WordPress/gutenberg/pull/53055)) +- List: Allow ENTER on multi-selection. ([52947](https://github.com/WordPress/gutenberg/pull/52947)) +- List: Fix merging nested lists. ([52949](https://github.com/WordPress/gutenberg/pull/52949)) + +#### Design Tools +- Borders: Prevent console error when clearing custom border color. ([52963](https://github.com/WordPress/gutenberg/pull/52963)) +- Check if spacing tool is defined before displaying controls. ([53008](https://github.com/WordPress/gutenberg/pull/53008)) + +#### Distraction Free +- Add missing command in Site Editor. ([52868](https://github.com/WordPress/gutenberg/pull/52868)) +- Distraction Free Keyboard Shortcut: Fix notices in Site Editor. ([52867](https://github.com/WordPress/gutenberg/pull/52867)) + +#### Layout +- Prevent the Dimensions UI from being displayed when the block does not support Dimensions. ([53092](https://github.com/WordPress/gutenberg/pull/53092)) + +#### List View +- Ensure `onBlockDrop` does not fire if there is no target. ([52959](https://github.com/WordPress/gutenberg/pull/52959)) + +#### Template Editor +- Site Editor: Don't navigate to the patterns in Template Parts mode. ([52884](https://github.com/WordPress/gutenberg/pull/52884)) + +#### Navigation Menus +- Navigation: Backport Core changes for the navigation fallback. ([52878](https://github.com/WordPress/gutenberg/pull/52878)) + +#### REST API +- Global styles revisions: Update `private` methods to `protected`. ([52748](https://github.com/WordPress/gutenberg/pull/52748)) + +#### Block API +- Parser / Site Editor: Ensure `autop` is not run when freeform block is set to core/html. ([52716](https://github.com/WordPress/gutenberg/pull/52716)) + + +### Accessibility + +- Fix regression with Edit site Navigate regions. ([52940](https://github.com/WordPress/gutenberg/pull/52940)) +- Return focus more from focus return hook. ([52710](https://github.com/WordPress/gutenberg/pull/52710)) +- [Commands]: Add `aria-activedescendant` attribute to suggestions. ([52930](https://github.com/WordPress/gutenberg/pull/52930)) + +#### Components +- ColorPalette, BorderControl: Don't hyphenate HEX value in `aria-label`. ([52932](https://github.com/WordPress/gutenberg/pull/52932)) +- `Modal`: Fix loss of focus when clicking outside. ([52653](https://github.com/WordPress/gutenberg/pull/52653)) + +#### Patterns +- My Patterns page: Increase color contrast for the toggle group. ([52678](https://github.com/WordPress/gutenberg/pull/52678)) + +#### Post Editor +- Improve consistency of the Post Editor and Site Editor Document actions. ([52246](https://github.com/WordPress/gutenberg/pull/52246)) + + +### Performance + +- Replace `array_key_exists()` with `isset()` check. ([53098](https://github.com/WordPress/gutenberg/pull/53098)) + +#### Block Library +- Footnotes: Use static closures when not using '$this'. ([52781](https://github.com/WordPress/gutenberg/pull/52781)) + + +### Experiments + +#### Block Library +- Set freeform handler only if Classic block exists. ([52936](https://github.com/WordPress/gutenberg/pull/52936)) +- Backend handle freeform blocks with TinyMCE removal. ([52938](https://github.com/WordPress/gutenberg/pull/52938)) +- Fix TinyMCE removal for heartbeat requests. ([52935](https://github.com/WordPress/gutenberg/pull/52935)) + +#### Interactivity API +- Improve the Interactivity API priority levels logic. ([52323](https://github.com/WordPress/gutenberg/pull/52323)) +- Remove the `wp-show` directive temporarily. ([53240](https://github.com/WordPress/gutenberg/pull/53240)) + +#### Block API +- Auto-inserting blocks on the frontend and in the editor (via REST API). ([51449](https://github.com/WordPress/gutenberg/pull/51449)) + + +### Documentation + +- API Reference documentation for Interactivity API. ([52948](https://github.com/WordPress/gutenberg/pull/52948)) +- Adding description of the `--no-watch` option. ([53139](https://github.com/WordPress/gutenberg/pull/53139)) +- Adds documentation about selectors. ([52941](https://github.com/WordPress/gutenberg/pull/52941)) +- Add Block API Version 3. ([53046](https://github.com/WordPress/gutenberg/pull/53046)) +- Added missing images via developer.wp.org site. ([53051](https://github.com/WordPress/gutenberg/pull/53051)) +- Clarify that `blockGap` support depends on layout support. ([53254](https://github.com/WordPress/gutenberg/pull/53254)) +- Interactivity API > Getting Started Guide - minor adjustments. ([52786](https://github.com/WordPress/gutenberg/pull/52786)) +- Update Appearance Tools. ([52785](https://github.com/WordPress/gutenberg/pull/52785)) +- Open "docs" folder for the Interactivity API package and Getting Started Guide. ([52462](https://github.com/WordPress/gutenberg/pull/52462)) +- Update the Gutenberg release process documentation. ([52955](https://github.com/WordPress/gutenberg/pull/52955)) + + +### Code Quality + +- Add options for debugging PHP unit tests in package.json. ([52778](https://github.com/WordPress/gutenberg/pull/52778)) +- Block Editor: Fix ESLint warning for the `useBlockEditingMode` hook. ([53218](https://github.com/WordPress/gutenberg/pull/53218)) +- Block Editor: Simplify check in 'withBlockControls' styles hook. ([53227](https://github.com/WordPress/gutenberg/pull/53227)) +- BlockVariationPicker: Remove unused `withSelect`. ([53100](https://github.com/WordPress/gutenberg/pull/53100)) +- Enforce checks against redeclaration for functions and classes. ([52696](https://github.com/WordPress/gutenberg/pull/52696)) +- Improve the efficiency of the `useDebouncedInput` hook. ([53263](https://github.com/WordPress/gutenberg/pull/53263)) +- Refactor `useShowMoversGestures` hook. ([52792](https://github.com/WordPress/gutenberg/pull/52792)) +- Remove `withSpokenMessages` HoC from the Link format. ([53106](https://github.com/WordPress/gutenberg/pull/53106)) +- Site Editor: Remove unnecessary hook from 'PageTemplates'. ([52903](https://github.com/WordPress/gutenberg/pull/52903)) +- [Create Block] Add support for the example property and add template defaults. ([52803](https://github.com/WordPress/gutenberg/pull/52803)) + +#### Block Library +- Add PHP since annotations. ([52820](https://github.com/WordPress/gutenberg/pull/52820)) +- Footnotes: Add missing _ in revision field filter. ([53135](https://github.com/WordPress/gutenberg/pull/53135)) +- Refactor navigation title usage. ([52807](https://github.com/WordPress/gutenberg/pull/52807)) +- Template Part Block: Use `get_block_file_template` for rendering. ([52892](https://github.com/WordPress/gutenberg/pull/52892)) + +#### Global Styles +- Don't use named arguments for `sprintf`. ([52782](https://github.com/WordPress/gutenberg/pull/52782)) +- Remove experimental setting for interactivity API and behaviors. ([52833](https://github.com/WordPress/gutenberg/pull/52833)) +- Site editor: Update function name. ([52869](https://github.com/WordPress/gutenberg/pull/52869)) +- Update PHP unit tests. ([52819](https://github.com/WordPress/gutenberg/pull/52819)) + +#### Interactivity API +- Move Store's data encoding to the `echo` call. ([51974](https://github.com/WordPress/gutenberg/pull/51974)) +- Update the `block.json` schema to include Behavior supports. ([52895](https://github.com/WordPress/gutenberg/pull/52895)) + +#### Typography +- Fluid typography: Rename viewport variables. ([53082](https://github.com/WordPress/gutenberg/pull/53082)) + +#### Components +- Update framer-motion to 10.13.0. ([52804](https://github.com/WordPress/gutenberg/pull/52804)) + +#### Themes +- Behaviors - Lightbox: Update theme.json schema. ([51156](https://github.com/WordPress/gutenberg/pull/51156)) + + +### Tools + +- Add GH action to enforce PR labels. ([52760](https://github.com/WordPress/gutenberg/pull/52760)) +- Changelog automation: + - Make Accessibility a top-level section. ([52900](https://github.com/WordPress/gutenberg/pull/52900)) + - Update to work with consolidated a11y labels. ([52896](https://github.com/WordPress/gutenberg/pull/52896)) + - Use the correct label to filter Mobile app PRs. ([53024](https://github.com/WordPress/gutenberg/pull/53024)) +- Enforce PR labels: + - Change permissions to `read`. ([52980](https://github.com/WordPress/gutenberg/pull/52980)) + - Fully re-enabling the pre-merge check. ([52990](https://github.com/WordPress/gutenberg/pull/52990)) + - Make PR label checks non-blocking to merge while trying a different GH token setting. ([52975](https://github.com/WordPress/gutenberg/pull/52975)) + - Try using a different token. ([52979](https://github.com/WordPress/gutenberg/pull/52979)) + - Use `pull_request_target` trigger to work with PRs coming from forks. ([52981](https://github.com/WordPress/gutenberg/pull/52981)) +- Use proper casing on PR template. ([52999](https://github.com/WordPress/gutenberg/pull/52999)) + +#### Testing +- Add end-to-end tests for Behaviors in the site editor. ([52809](https://github.com/WordPress/gutenberg/pull/52809)) +- Ignore local test theme folders created by `wp-env`. ([53031](https://github.com/WordPress/gutenberg/pull/53031)) +- Improve slug generation & matching in request utils. ([52414](https://github.com/WordPress/gutenberg/pull/52414)) +- Migrate 'iframed inline styles' end-to-end tests to Playwright. ([53269](https://github.com/WordPress/gutenberg/pull/53269)) +- Migrate Allowed Block Test to Playwright. ([53171](https://github.com/WordPress/gutenberg/pull/53171)) +- Patterns: Reinstate template parts mode spec. ([52780](https://github.com/WordPress/gutenberg/pull/52780)) +- Route to published post instead of homepage on navigation end-to-end tests. ([52802](https://github.com/WordPress/gutenberg/pull/52802)) +- Temporarily skip widget import end-to-end test. ([53226](https://github.com/WordPress/gutenberg/pull/53226)) +- Update end-to-end tests that use code editor. ([52788](https://github.com/WordPress/gutenberg/pull/52788)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @Armondal: Adding description of the `--no-watch` option. ([53139](https://github.com/WordPress/gutenberg/pull/53139)) +- @lunaluna: Video: Fixing styles that vertical alignment of the video. ([53131](https://github.com/WordPress/gutenberg/pull/53131)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @andrewserong @anton-vlasenko @Armondal @artemiomorales @audrasjb @bph @c4rl0sbr4v0 @carolinan @chad1008 @danielbachhuber @DAreRodz @dcalhoun @derekblank @draganescu @ellatrix @fluiddot @geriux @getdave @glendaviesnz @hellofromtonya @jameskoster @jasmussen @jeherve @jeryj @jordesign @jorgefilipecosta @jsnajdr @juanmaguitar @kevin940726 @luisherranz @lunaluna @Mamaduka @mburridge @michalczaplinski @mikachan @mirka @ndiego @noahtallen @noisysocks @ntsekouras @ockham @pedro-mendonca @pooja-muchandikar @priethor @ramonjd @richtabor @ryanwelcher @scruffian @shimotmk @Soean @stokesman @swissspidy @t-hamano @tellthemachines @torounit @tyxla @westonruter @WunderBart + + = 16.3.0 = ## Changelog From ae4385a8a672c048821bc6fb42693e7bb598a367 Mon Sep 17 00:00:00 2001 From: Rich Tabor Date: Wed, 2 Aug 2023 17:24:36 -0400 Subject: [PATCH 045/150] Refresh command palette styling (#53117) * Add a search icon, update placeholder * Clean up styles * Add back mark styling * Remove space between groups * Add back mark font-weight styling * $grid-unit-05 to $radius-block-ui --- .../commands/src/components/command-menu.js | 6 ++-- packages/commands/src/components/style.scss | 34 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index c0e6062d934dc2..f2ea39d112da67 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -24,7 +24,7 @@ import { store as keyboardShortcutsStore, useShortcut, } from '@wordpress/keyboard-shortcuts'; -import { Icon } from '@wordpress/icons'; +import { Icon, search as inputIcon } from '@wordpress/icons'; /** * Internal dependencies @@ -168,8 +168,9 @@ function CommandInput( { isOpen, search, setSearch } ) { ref={ commandMenuInput } value={ search } onValueChange={ setSearch } - placeholder={ __( 'Type a command or search' ) } + placeholder={ __( 'Search for commands' ) } aria-activedescendant={ selectedItemId } + icon={ search } /> ); } @@ -260,6 +261,7 @@ export function CommandMenu() { onKeyDown={ onKeyDown } >
    + [cmdk-list] { - max-height: 400px; + max-height: 368px; // Specific to not have commands overflow oddly. overflow: auto; - & > [cmdk-list-sizer] :has([cmdk-group-items]:not(:empty)) { - padding: $grid-unit; + & > [cmdk-list-sizer]:not(:empty) { + padding: 0 $grid-unit $grid-unit; } } @@ -105,7 +105,7 @@ justify-content: center; height: $grid-unit-80; white-space: pre-wrap; - color: $gray-800; + color: $gray-900; } [cmdk-loading] { @@ -115,10 +115,14 @@ [cmdk-list-sizer] { position: relative; } +} - [cmdk-group]:has([cmdk-group-items]:not(:empty)):not([hidden]) + [cmdk-group]:has([cmdk-group-items]:not(:empty)) { - border-top: $border-width solid $gray-200; - } +.commands-command-menu__item span { + // Ensure commands do not run off the edge (great for post titles). + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .commands-command-menu__item mark { From 960a0c3002da274cfa83b353c5f871449fb9af85 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 3 Aug 2023 01:30:56 +0400 Subject: [PATCH 046/150] Commands: Use the same Preview target tab (#53242) --- .../edit-post/src/hooks/commands/use-common-commands.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/hooks/commands/use-common-commands.js b/packages/edit-post/src/hooks/commands/use-common-commands.js index d07f9695c4dcf7..7dd45aa83f9daf 100644 --- a/packages/edit-post/src/hooks/commands/use-common-commands.js +++ b/packages/edit-post/src/hooks/commands/use-common-commands.js @@ -59,6 +59,7 @@ export default function useCommonCommands() { const { toggle } = useDispatch( preferencesStore ); const { createInfoNotice } = useDispatch( noticesStore ); const { __unstableSaveForPreview } = useDispatch( editorStore ); + const { getCurrentPostId } = useSelect( editorStore ); useCommand( { name: 'core/open-settings-sidebar', @@ -214,8 +215,9 @@ export default function useCommonCommands() { icon: external, callback: async ( { close } ) => { close(); - const link = await __unstableSaveForPreview( {} ); - window.open( link, '_blank' ); + const postId = getCurrentPostId(); + const link = await __unstableSaveForPreview(); + window.open( link, `wp-preview-${ postId }` ); }, } ); } From 16c13456fa771431d9923f9e9af5526af466d239 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:51:15 +1000 Subject: [PATCH 047/150] Footnotes: Fix recursion into updating attributes when attributes is not an object (#53257) --- packages/core-data/src/entity-provider.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index fad9fe693a575b..cf15c423456a29 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -221,6 +221,15 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { ); function updateAttributes( attributes ) { + // Only attempt to update attributes, if attributes is an object. + if ( + ! attributes || + Array.isArray( attributes ) || + typeof attributes !== 'object' + ) { + return attributes; + } + attributes = { ...attributes }; for ( const key in attributes ) { From 4ce3059466a2f7f4eaed8b170cd6a5d9001c8f29 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Thu, 3 Aug 2023 11:39:33 +1000 Subject: [PATCH 048/150] Don't add root padding to children of flex and grid layout blocks. (#53259) * Don't add root padding to children of flex layout blocks. * Add rule for grid too --- lib/class-wp-theme-json-gutenberg.php | 2 +- .../src/components/global-styles/use-global-styles-output.js | 2 +- phpunit/class-wp-theme-json-test.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 2672d090aacbda..dd8bf832174e67 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2502,7 +2502,7 @@ public function get_root_layout_rules( $selector, $block_metadata ) { // The above rule is negated for alignfull children of nested containers. $css .= '.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }'; // Some of the children of alignfull blocks without content width should also get padding: text blocks and non-alignfull container blocks. - $css .= '.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; + $css .= '.has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; // The above rule also has to be negated for blocks inside nested `.has-global-padding` blocks. $css .= '.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }'; } diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index ec1a6270917f0a..1db7e46c6eb775 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -793,7 +793,7 @@ export const toStyles = ( .has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; } .has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); } .has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; } - .has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } + .has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } .has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0;`; } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index bd1d578a1297b6..03e9fa99073e6c 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -1391,7 +1391,7 @@ public function test_get_styles_for_block_with_padding_aware_alignments() { 'selector' => 'body', ); - $expected = 'body { margin: 0;}.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; + $expected = 'body { margin: 0;}.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }.has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ); $style_rules = $theme_json->get_styles_for_block( $metadata ); $this->assertEquals( $expected, $root_rules . $style_rules ); From e78edcefc628a6e8654e8e12b80ac1586a2d5f3c Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 3 Aug 2023 13:59:24 +1200 Subject: [PATCH 049/150] Cover block: Add integration tests to check isDark settings (#53256) --- packages/block-library/src/cover/test/edit.js | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js index e399f379e21553..dce4d7bd7e0213 100644 --- a/packages/block-library/src/cover/test/edit.js +++ b/packages/block-library/src/cover/test/edit.js @@ -18,7 +18,10 @@ const defaultSettings = { defaultPalette: true, defaultGradients: true, palette: { - default: [ { name: 'Black', slug: 'black', color: '#000000' } ], + default: [ + { name: 'Black', slug: 'black', color: '#000000' }, + { name: 'White', slug: 'white', color: '#ffffff' }, + ], }, }, }, @@ -375,4 +378,51 @@ describe( 'Cover block', () => { } ); } ); } ); + + describe( 'isDark settings', () => { + test( 'should toggle is-light class if background changed from light to dark', async () => { + await setup(); + const colorPicker = screen.getByRole( 'button', { + name: 'Color: White', + } ); + await userEvent.click( colorPicker ); + + const coverBlock = screen.getByLabelText( 'Block: Cover' ); + + expect( coverBlock ).toHaveClass( 'is-light' ); + + await selectBlock( 'Block: Cover' ); + await userEvent.click( + screen.getByRole( 'tab', { + name: 'Styles', + } ) + ); + await userEvent.click( screen.getByText( 'Overlay' ) ); + const popupColorPicker = screen.getByRole( 'button', { + name: 'Color: Black', + } ); + await userEvent.click( popupColorPicker ); + expect( coverBlock ).not.toHaveClass( 'is-light' ); + } ); + test( 'should apply is-light class if overlay color is removed', async () => { + await setup(); + await createAndSelectBlock(); + const coverBlock = screen.getByLabelText( 'Block: Cover' ); + expect( coverBlock ).not.toHaveClass( 'is-light' ); + + await userEvent.click( + screen.getByRole( 'tab', { + name: 'Styles', + } ) + ); + await userEvent.click( screen.getByText( 'Overlay' ) ); + // The default color is black, so clicking the black color option will remove the background color, + // which should remove the isDark setting and assign the is-light class. + const popupColorPicker = screen.getByRole( 'button', { + name: 'Color: Black', + } ); + await userEvent.click( popupColorPicker ); + expect( coverBlock ).toHaveClass( 'is-light' ); + } ); + } ); } ); From 9778c8e2f201eddd6a36bd2fdfbd13a957628b8d Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:43:51 +0900 Subject: [PATCH 050/150] Fix: Sync status overlaps for some languages in Patterns post type page (#53243) --- packages/editor/src/components/post-sync-status/style.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-sync-status/style.scss b/packages/editor/src/components/post-sync-status/style.scss index 7f81a327443ee1..90a75c86bf466d 100644 --- a/packages/editor/src/components/post-sync-status/style.scss +++ b/packages/editor/src/components/post-sync-status/style.scss @@ -2,15 +2,18 @@ width: 100%; position: relative; justify-content: flex-start; + align-items: flex-start; > span { display: block; width: 45%; flex-shrink: 0; + padding: $grid-unit-15 * 0.5 0; + word-break: break-word; } > div { // Match padding on tertiary buttons for alignment. - padding-left: $grid-unit-15; + padding: $grid-unit-15 * 0.5 0 $grid-unit-15 * 0.5 $grid-unit-15; } } From 143c5c8c207571b3bc895cf9851029a3decd6113 Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 3 Aug 2023 13:57:46 +1000 Subject: [PATCH 051/150] Global styles revisions: add a reset to default revision (#52965) * Inserting a reset object as the first "revision" to reset to theme defaults * Update tests * Update tests * - Use rendered Theme name as the revisions button - Ensure that any undefined properties, e.g., styles, settings, are set as empty objects so that any theme defaults are correctly displayed by default * Added a test to verify reset UI --- .../global-styles/screen-revisions/index.js | 10 +-- .../screen-revisions/revisions-buttons.js | 67 +++++++++++++------ .../test/use-global-styles-revisions.js | 10 +++ .../use-global-styles-revisions.js | 6 ++ .../src/components/global-styles/ui.js | 2 +- .../user-global-styles-revisions.spec.js | 20 ++++++ 6 files changed, 89 insertions(+), 26 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/index.js b/packages/edit-site/src/components/global-styles/screen-revisions/index.js index acdc4fdc3dc557..7af3d40ec2c754 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/index.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/index.js @@ -79,9 +79,9 @@ function ScreenRevisions() { const selectRevision = ( revision ) => { setGlobalStylesRevision( { - styles: revision?.styles, - settings: revision?.settings, - behaviors: revision?.behaviors, + styles: revision?.styles || {}, + settings: revision?.settings || {}, + behaviors: revision?.behaviors || {}, id: revision?.id, } ); setSelectedRevisionId( revision?.id ); @@ -137,7 +137,9 @@ function ScreenRevisions() { } } } > - { __( 'Apply' ) } + { globalStylesRevision?.id === 'parent' + ? __( 'Reset to defaults' ) + : __( 'Apply' ) } ) } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index 801f3c02363fbd..feec0f25ac8823 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -9,6 +9,8 @@ import classnames from 'classnames'; import { __, sprintf } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date'; +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; /** * Returns a button label for the revision. @@ -19,6 +21,10 @@ import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date'; function getRevisionLabel( revision ) { const authorDisplayName = revision?.author?.name || __( 'User' ); + if ( 'parent' === revision?.id ) { + return __( 'Reset the styles to the theme defaults' ); + } + if ( 'unsaved' === revision?.id ) { return sprintf( /* translators: %s author display name */ @@ -26,6 +32,7 @@ function getRevisionLabel( revision ) { authorDisplayName ); } + const formattedDate = dateI18n( getSettings().formats.datetimeAbbreviated, getDate( revision?.modified ) @@ -58,6 +65,10 @@ function getRevisionLabel( revision ) { * @return {JSX.Element} The modal component. */ function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { + const currentTheme = useSelect( + ( select ) => select( coreStore ).getCurrentTheme(), + [] + ); return (
      - - - - { isUnsaved - ? sprintf( - /* translators: %s author display name */ - __( 'Unsaved changes by %s' ), - authorDisplayName - ) - : sprintf( - /* translators: %s author display name */ - __( 'Changes saved by %s' ), - authorDisplayName - ) } + { isReset ? ( + + { __( 'Default styles' ) } + + { currentTheme?.name?.rendered || + currentTheme?.stylesheet } + + + ) : ( + + + + { isUnsaved + ? sprintf( + /* translators: %s author display name */ + __( + 'Unsaved changes by %s' + ), + authorDisplayName + ) + : sprintf( + /* translators: %s author display name */ + __( 'Changes saved by %s' ), + authorDisplayName + ) } - { + { + - + ) } ); diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js b/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js index 8f618a4897edca..4b03e38fe34d27 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js @@ -71,6 +71,11 @@ describe( 'useGlobalStylesRevisions', () => { settings: {}, styles: {}, }, + { + id: 'parent', + settings: {}, + styles: {}, + }, ] ); } ); @@ -106,6 +111,11 @@ describe( 'useGlobalStylesRevisions', () => { settings: {}, styles: {}, }, + { + id: 'parent', + settings: {}, + styles: {}, + }, ] ); } ); diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js index bc82a5f2b12932..02e4a63154ec6b 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js @@ -99,6 +99,12 @@ export default function useGlobalStylesRevisions() { _modifiedRevisions.unshift( unsavedRevision ); } + + _modifiedRevisions.push( { + id: 'parent', + styles: {}, + settings: {}, + } ); } return { diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 8d62da34f49668..3e4ce1844395a5 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -139,7 +139,7 @@ function GlobalStylesRevisionsMenu() { goTo( '/revisions' ); setEditorCanvasContainerView( 'global-styles-revisions' ); }; - const hasRevisions = revisionsCount >= 2; + const hasRevisions = revisionsCount >= 1; return ( diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js index 2aadc2f70dc402..406e31f9f88f31 100644 --- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js +++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js @@ -68,6 +68,7 @@ test.describe( 'Global styles revisions', () => { name: /^Changes saved by /, } ); + // There should be 2 revisions not including the reset to theme defaults button. await expect( revisionButtons ).toHaveCount( currentRevisions.length + 2 ); @@ -116,6 +117,25 @@ test.describe( 'Global styles revisions', () => { .click(); await editor.saveSiteEditorEntities(); } ); + + test( 'should have a reset to defaults button', async ( { + page, + editor, + userGlobalStylesRevisions, + } ) => { + await editor.canvas.click( 'body' ); + await userGlobalStylesRevisions.openStylesPanel(); + await userGlobalStylesRevisions.openRevisions(); + const lastRevisionButton = page + .getByLabel( 'Global styles revisions' ) + .getByRole( 'button' ) + .last(); + await expect( lastRevisionButton ).toContainText( 'Default styles' ); + await lastRevisionButton.click(); + await expect( + page.getByRole( 'button', { name: 'Reset to defaults' } ) + ).toBeVisible(); + } ); } ); class UserGlobalStylesRevisions { From b2e5f0d6556bdd140bee1e728e70f68e11c21ac4 Mon Sep 17 00:00:00 2001 From: Gopal Krishnan Date: Thu, 3 Aug 2023 15:03:02 +1000 Subject: [PATCH 052/150] Add README to the useSetting component (#53162) * Add a README for the useSetting hook * Tweak the readme to add more detail --- .../src/components/use-setting/README.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/block-editor/src/components/use-setting/README.md diff --git a/packages/block-editor/src/components/use-setting/README.md b/packages/block-editor/src/components/use-setting/README.md new file mode 100644 index 00000000000000..96f3c68fbcfade --- /dev/null +++ b/packages/block-editor/src/components/use-setting/README.md @@ -0,0 +1,39 @@ +## Use Setting + +`useSetting` is a hook that will retrive the setting for the block instance that's in use. + +It does the lookup of the setting in the following order: + +1. Third parties can provide the settings for the block using the filter `blockEditor.useSetting.before`. +2. If no third parties have provided this setting, then it looks up in the block instance hierachy starting from the current block and working its way upwards to its ancestors. +3. If that doesn't prove to be successful in getting a value, then it falls back to the settings from the block editor store. +4. If none of the above steps prove to be successful, then it's likely to be a deprecated setting and the deprecated setting is used instead. + +## Table of contents + +1. [Development guidelines](#development-guidelines) + +## Development guidelines + +### Usage + +This will fetch the default color palette based on the block instance. + +```jsx +import { useSetting } from '@wordpress/block-editor'; + +const defaultColorPalette = useSetting( 'color.palette.default' ); +``` + +Refer [here](https://github.com/WordPress/gutenberg/blob/HEAD/docs/how-to-guides/curating-the-editor-experience.md?plain=1#L330) in order to understand how the filter mentioned above `blockEditor.useSetting.before` can be used. + +### Props + +This hooks accepts the following props. + +#### `path` + +- **Type:** `String` +- **Default:** `undefined` + +The path to the setting that is to be used for a block. Ex: `typography.fontSizes` \ No newline at end of file From 9020d4be5f769d9ab936bcc8627abee11d08d051 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Thu, 3 Aug 2023 09:12:23 +0200 Subject: [PATCH 053/150] Mobile - Fix iOS Focus loop for RichText components (#53217) * Mobile - Update changelog * fix: Avoid iOS block appender focus loop The focus callback triggered by Aztec-based programmatic focus events can result in focus loops between rich text elements. Android: This intentional no-op function prevents focus loops originating when the native Aztec module programmatically focuses the instance. The no-op is explicitly passed as an `onFocus` prop to avoid future prop spreading from inadvertently introducing focus loops. The user-facing focus of the element is handled by `onPress` instead. See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/302 iOS: Programmatic focus from the native Aztec module is required to ensure the React-based `TextStateInput` ref is properly set when focus is *returned* to an instance, e.g. dismissing a bottom sheet. If the ref is not updated, attempts to dismiss the keyboard via the `ToolbarButton` will fail. See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/702 The Android keyboard is, likely erroneously, already dismissed in the contexts where programmatic focus may be required on iOS. - https://github.com/WordPress/gutenberg/issues/28748 - https://github.com/WordPress/gutenberg/issues/29048 - https://github.com/wordpress-mobile/WordPress-Android/issues/16167 Programmatic swapping focus from element to another often leads to focus loops, only delegate the programmatic focus if there are no elements focused. See: https://github.com/wordpress-mobile/WordPress-iOS/issues/18783 * fix: Programmatic Aztec input focus only updates internal ref Programmatically swapping input focus creates an infinite loop if the user taps a different input in between the programmatic focus and the resulting update to the React Native TextInputState focused element ref. To mitigate this, the Aztec now updates the focused element ref, but does not call the native focus methods. See: https://github.com/wordpress-mobile/WordPress-iOS/issues/18783 * Mobile - AztecView - Check for isFocused before forcing the focus * Mobile - DefaultBlockAppender and BlockList Footer placeholders - Removes inline functions and other minor code style changes * Mobile - AztecView - Trigger _onFocus within _onAztecFocus to prevent having a RichText component focused while another block is selected --------- Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --- .../src/components/block-list/index.native.js | 28 +++++----- .../default-block-appender/index.native.js | 24 +++++---- .../react-native-aztec/src/AztecInputState.js | 9 ++++ packages/react-native-aztec/src/AztecView.js | 51 +++++++++++++++---- packages/react-native-editor/CHANGELOG.md | 1 + 5 files changed, 79 insertions(+), 34 deletions(-) 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 da367ecc0ebea6..810e23e4c1442a 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -1,12 +1,12 @@ /** * External dependencies */ -import { View, Platform, TouchableWithoutFeedback } from 'react-native'; +import { View, Platform, Pressable } from 'react-native'; /** * WordPress dependencies */ -import { useRef, useState } from '@wordpress/element'; +import { useRef, useState, useCallback } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; import { @@ -372,20 +372,20 @@ function Footer( { renderFooterAppender, withFooter, } ) { + const onAddParagraphBlock = useCallback( () => { + const paragraphBlock = createBlock( 'core/paragraph' ); + addBlockToEndOfPost( paragraphBlock ); + }, [ addBlockToEndOfPost ] ); + if ( ! isReadOnly && withFooter ) { return ( - <> - { - const paragraphBlock = createBlock( 'core/paragraph' ); - addBlockToEndOfPost( paragraphBlock ); - } } - > - - - + + + ); } else if ( renderFooterAppender ) { return { renderFooterAppender() }; diff --git a/packages/block-editor/src/components/default-block-appender/index.native.js b/packages/block-editor/src/components/default-block-appender/index.native.js index dae0750b8e30d4..82ff8b7c8d4029 100644 --- a/packages/block-editor/src/components/default-block-appender/index.native.js +++ b/packages/block-editor/src/components/default-block-appender/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { TouchableWithoutFeedback, View } from 'react-native'; +import { Pressable, View } from 'react-native'; /** * WordPress dependencies @@ -20,6 +20,9 @@ import BlockInsertionPoint from '../block-list/insertion-point'; import styles from './style.scss'; import { store as blockEditorStore } from '../../store'; +const hitSlop = { top: 22, bottom: 22, left: 22, right: 22 }; +const noop = () => {}; + export function DefaultBlockAppender( { isLocked, isVisible, @@ -37,22 +40,21 @@ export function DefaultBlockAppender( { ? decodeEntities( placeholder ) : __( 'Start writing…' ); + const appenderStyles = [ + styles.blockHolder, + showSeparator && containerStyle, + ]; + return ( - - + + { showSeparator ? ( ) : ( - {} } /> + ) } - + ); } diff --git a/packages/react-native-aztec/src/AztecInputState.js b/packages/react-native-aztec/src/AztecInputState.js index 8a1737118d1d13..5f0a1dd7596284 100644 --- a/packages/react-native-aztec/src/AztecInputState.js +++ b/packages/react-native-aztec/src/AztecInputState.js @@ -116,6 +116,15 @@ export const notifyInputChange = () => { } }; +/** + * Sets the current focused element ref held within TextInputState. + * + * @param {RefObject} element Element to be set as the focused element. + */ +export const focusInput = ( element ) => { + TextInputState.focusInput( element ); +}; + /** * Focuses the specified element. * diff --git a/packages/react-native-aztec/src/AztecView.js b/packages/react-native-aztec/src/AztecView.js index 4d90d13974c8ec..e05737f5fb0f99 100644 --- a/packages/react-native-aztec/src/AztecView.js +++ b/packages/react-native-aztec/src/AztecView.js @@ -237,14 +237,49 @@ class AztecView extends Component { } _onAztecFocus( event ) { - // IMPORTANT: the onFocus events from Aztec are thrown away on Android as these are handled by onPress() in the upper level. - // It's necessary to do this otherwise onFocus may be set by `{...otherProps}` and thus the onPress + onFocus - // combination generate an infinite loop as described in https://github.com/wordpress-mobile/gutenberg-mobile/issues/302 - // For iOS, this is necessary to let the system know when Aztec was focused programatically. - if ( Platform.OS === 'ios' ) { + // IMPORTANT: This function serves two purposes: + // + // Android: This intentional no-op function prevents focus loops originating + // when the native Aztec module programmatically focuses the instance. The + // no-op is explicitly passed as an `onFocus` prop to avoid future prop + // spreading from inadvertently introducing focus loops. The user-facing + // focus of the element is handled by `onPress` instead. + // + // See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/302 + // + // iOS: Programmatic focus from the native Aztec module is required to + // ensure the React-based `TextStateInput` ref is properly set when focus + // is *returned* to an instance, e.g. dismissing a bottom sheet. If the ref + // is not updated, attempts to dismiss the keyboard via the `ToolbarButton` + // will fail. + // + // See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/702 + if ( + // The Android keyboard is, likely erroneously, already dismissed in the + // contexts where programmatic focus may be required on iOS. + // + // - https://github.com/WordPress/gutenberg/issues/28748 + // - https://github.com/WordPress/gutenberg/issues/29048 + // - https://github.com/wordpress-mobile/WordPress-Android/issues/16167 + Platform.OS === 'ios' + ) { this.updateCaretData( event ); - this._onPress( event ); + if ( ! this.isFocused() ) { + // Programmatically swapping input focus creates an infinite loop if the + // user taps a different input in between the programmatic focus and + // the resulting update to the React Native TextInputState focused element + // ref. To mitigate this, the below updates the focused element ref, but + // does not call the native focus methods. + // + // See: https://github.com/wordpress-mobile/WordPress-iOS/issues/18783 + AztecInputState.focusInput( this.aztecViewRef.current ); + + // Calling _onFocus is needed to trigger provided onFocus callbacks + // which are needed to prevent undesired results like having a focused + // TextInput when another element has the focus. + this._onFocus( event ); + } } } @@ -285,9 +320,7 @@ class AztecView extends Component { onBackspace={ this.props.onKeyDown && this._onBackspace } onKeyDown={ this.props.onKeyDown && this._onKeyDown } deleteEnter={ this.props.deleteEnter } - // IMPORTANT: the onFocus events are thrown away as these are handled by onPress() in the upper level. - // It's necessary to do this otherwise onFocus may be set by `{...otherProps}` and thus the onPress + onFocus - // combination generate an infinite loop as described in https://github.com/wordpress-mobile/gutenberg-mobile/issues/302 + // IMPORTANT: Do not remove the `onFocus` prop, see `_onAztecFocus` onFocus={ this._onAztecFocus } onBlur={ this._onBlur } ref={ this.aztecViewRef } diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index ff72e36c6ead33..de65021b44d752 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -15,6 +15,7 @@ For each user feature we should also add a importance categorization label to i - [**] Upgrade React Native to 0.71.11 [#51303] - [*] Upgrade Gradle to 8.2.1 & AGP to 8.1.0 [#52872] - [*] Fix Gallery block selection when adding media [#53127] +- [**] Fix iOS Focus loop for RichText components [#53217] ## 1.100.1 - [**] Add WP hook for registering non-core blocks [#52791] From 00a1b6e06c02bb546667017a70f2f72dc64d4127 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:58:41 +0200 Subject: [PATCH 054/150] Auto-inserting blocks: Minor fixes to address feedback (#53183) * Document possible values of `$relative_position` argument. * Rename `$anchor_block` param to `$anchor_block_type`, document better. * More explanatory PHPDoc for `gutenberg_auto_insert_block`; mention mutating. * Mention leveraging serialization depth-first traversal in PHPDoc. --- lib/experimental/auto-inserting-blocks.php | 28 +++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index 9df94fcbfcd8fb..90f3bdf15e509d 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -6,16 +6,24 @@ */ /** - * Return a function that auto-inserts blocks relative to a given block. + * Return a function that auto-inserts a block next to a given "anchor" block. + * + * The auto-inserted block can be inserted before or after the anchor block, + * or as the first or last child of the anchor block. + * + * Note that the returned function mutates the auto-inserted block's designated + * parent block by inserting into the parent's `innerBlocks` array, and by + * updating the parent's `innerContent` array accordingly. * * @param array $inserted_block The block to insert. * @param string $relative_position The position relative to the given block. - * @param string $anchor_block The block to insert relative to. + * Can be 'before', 'after', 'first_child', or 'last_child'. + * @param string $anchor_block_type The auto-inserted block will be inserted next to instances of this block type. * @return callable A function that accepts a block's content and returns the content with the inserted block. */ -function gutenberg_auto_insert_block( $inserted_block, $relative_position, $anchor_block ) { - return function( $block ) use ( $inserted_block, $relative_position, $anchor_block ) { - if ( $anchor_block === $block['blockName'] ) { +function gutenberg_auto_insert_block( $inserted_block, $relative_position, $anchor_block_type ) { + return function( $block ) use ( $inserted_block, $relative_position, $anchor_block_type ) { + if ( $anchor_block_type === $block['blockName'] ) { if ( 'first_child' === $relative_position ) { array_unshift( $block['innerBlocks'], $inserted_block ); // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) @@ -32,7 +40,7 @@ function gutenberg_auto_insert_block( $inserted_block, $relative_position, $anch return $block; } - $anchor_block_index = array_search( $anchor_block, array_column( $block['innerBlocks'], 'blockName' ), true ); + $anchor_block_index = array_search( $anchor_block_type, array_column( $block['innerBlocks'], 'blockName' ), true ); if ( false !== $anchor_block_index && ( 'after' === $relative_position || 'before' === $relative_position ) ) { if ( 'after' === $relative_position ) { $anchor_block_index++; @@ -144,7 +152,9 @@ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $an * * By parsing a block template's content and then reserializing it * via `gutenberg_serialize_blocks()`, we are able to run filters - * on the parsed blocks. + * on the parsed blocks. This allows us to modify (parsed) blocks during + * depth-first traversal already provided by the serialization process, + * rather than having to do so in a separate pass. * * @param WP_Block_Template[] $query_result Array of found block templates. * @return WP_Block_Template[] Updated array of found block templates. @@ -167,7 +177,9 @@ function gutenberg_parse_and_serialize_block_templates( $query_result ) { * * By parsing a block template's content and then reserializing it * via `gutenberg_serialize_blocks()`, we are able to run filters - * on the parsed blocks. + * on the parsed blocks. This allows us to modify (parsed) blocks during + * depth-first traversal already provided by the serialization process, + * rather than having to do so in a separate pass. * * @param WP_Block_Template|null $block_template The found block template, or null if there is none. */ From ebdeee342f484ca292bfcd32862255dec319a975 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 3 Aug 2023 16:27:05 +0400 Subject: [PATCH 055/150] Plugins: Introduce the 'usePluginContext' hook (#53291) --- packages/plugins/README.md | 8 ++++++++ packages/plugins/src/components/index.js | 2 +- .../src/components/plugin-context/index.tsx | 19 ++++++++++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 99bfd3d7bd74de..a0e8441513e5d7 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -182,6 +182,14 @@ _Returns_ - `WPPlugin | undefined`: The previous plugin settings object, if it has been successfully unregistered; otherwise `undefined`. +#### usePluginContext + +A hook that returns the plugin context. + +_Returns_ + +- `PluginContext`: Plugin context + #### withPluginContext A Higher Order Component used to inject Plugin context to the wrapped component. diff --git a/packages/plugins/src/components/index.js b/packages/plugins/src/components/index.js index dae012a5cadefc..12a9a96808bc6a 100644 --- a/packages/plugins/src/components/index.js +++ b/packages/plugins/src/components/index.js @@ -1,2 +1,2 @@ export { default as PluginArea } from './plugin-area'; -export { withPluginContext } from './plugin-context'; +export { usePluginContext, withPluginContext } from './plugin-context'; diff --git a/packages/plugins/src/components/plugin-context/index.tsx b/packages/plugins/src/components/plugin-context/index.tsx index fe4fa4cecfa074..76fbdabe048293 100644 --- a/packages/plugins/src/components/plugin-context/index.tsx +++ b/packages/plugins/src/components/plugin-context/index.tsx @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createContext } from '@wordpress/element'; +import { createContext, useContext } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; /** @@ -14,12 +14,21 @@ export interface PluginContext { icon: null | WPPlugin[ 'icon' ]; } -const { Consumer, Provider } = createContext< PluginContext >( { +const Context = createContext< PluginContext >( { name: null, icon: null, } ); -export { Provider as PluginContextProvider }; +export const PluginContextProvider = Context.Provider; + +/** + * A hook that returns the plugin context. + * + * @return {PluginContext} Plugin context + */ +export function usePluginContext() { + return useContext( Context ); +} /** * A Higher Order Component used to inject Plugin context to the @@ -39,13 +48,13 @@ export const withPluginContext = ( ) => createHigherOrderComponent( ( OriginalComponent ) => { return ( props ) => ( - + { ( context ) => ( ) } - + ); }, 'withPluginContext' ); From b56fadd89409a22b5a717a8a2f67a7438782ac1a Mon Sep 17 00:00:00 2001 From: Nick Diego Date: Thu, 3 Aug 2023 07:41:18 -0500 Subject: [PATCH 056/150] Consolidate and update the Building a block editor guide (#53159) * Consolidate and update custom block editor guide. * Grammary audit and formatting fixes. * Update screenshots. --- .../platform/custom-block-editor.md | 599 +++++++++++++++++ .../platform/custom-block-editor/README.md | 17 - .../platform/custom-block-editor/tutorial.md | 623 ------------------ docs/manifest.json | 8 +- docs/toc.json | 6 +- 5 files changed, 601 insertions(+), 652 deletions(-) create mode 100644 docs/how-to-guides/platform/custom-block-editor.md delete mode 100644 docs/how-to-guides/platform/custom-block-editor/README.md delete mode 100644 docs/how-to-guides/platform/custom-block-editor/tutorial.md diff --git a/docs/how-to-guides/platform/custom-block-editor.md b/docs/how-to-guides/platform/custom-block-editor.md new file mode 100644 index 00000000000000..bb119977e6986a --- /dev/null +++ b/docs/how-to-guides/platform/custom-block-editor.md @@ -0,0 +1,599 @@ +# Building a custom block editor + +The WordPress block editor is a powerful tool that allows you to create and format content in various ways. It is powered, in part, by the [`@wordpress/block-editor`](/packages/block-editor/README.md) package, which is a JavaScript library that provides the core functionality of the editor. + +This package can also be used to create custom block editors for virtually any other web application. This means that you can use the same blocks and block editing experience outside of WordPress. + +![alt text](https://developer.wordpress.org/files/2023/07/custom-block-editor.png 'The Standalone Editor instance populated with example Blocks within a custom WordPress admin page.') + +This flexibility and interoperability makes blocks a powerful tool for building and managing content across multiple applications. It also makes it simpler for developers to create content editors that work best for their users. + +This guide covers the basics of creating your first custom block editor. + +## Table of contents + +- [Introduction](#introduction) +- [Code Syntax](#code-syntax) +- [What you're going to be building](#what-youre-going-to-be-building) +- [Plugin setup and organization](#plugin-setup-and-organization) +- [The "Core" of the editor](#the-core-of-the-editor) +- [Creating the custom "Block Editor" page](#creating-the-custom-block-editor-page) +- [Registering and rendering the custom block editor](#registering-and-rendering-the-custom-block-editor) +- [Reviewing the `` component](#reviewing-the-editor-component) +- [The custom ``](#the-custom-blockeditor) +- [Reviewing the sidebar](#reviewing-the-sidebar) +- [Block persistence](#block-persistence) +- [Wrapping up](#wrapping-up) + +## Introduction + +With its many packages and components, the Gutenberg codebase can be daunting at first. But at its core, it's all about managing and editing blocks. So if you want to work on the editor, it's essential to understand how block editing works at a fundamental level. + +This guide will walk you through building a fully functioning, custom block editor "instance" within WordPress. Along the way, we'll introduce you to the key packages and components, so you can see how the block editor works under the hood. + +By the end of this article, you will have a solid understanding of the block editor's inner workings and be well on your way to creating your own block editor instances. + + + +## Code syntax + +The code snippets in this guide use JSX syntax. However, you could use plain JavaScript if you prefer. However, once familiar with JSX, many developers find it easier to read and write, so most code examples in the Block Editor Handbook use this syntax. + +## What you're going to be building + +Throughout this guide, you will create an (almost) fully functioning block editor instance. The result will look something like this: + +![The Standalone Editor instance populated with example Blocks within a custom WordPress admin page](https://developer.wordpress.org/files/2023/07/custom-block-editor.png) + +While it looks similar, this editor will not be the same _Block Editor_ you are familiar with when creating posts and pages in WordPress. Instead, it will be an entirely custom instance that will live within a custom WordPress admin page called "Block Editor." + +The editor will have the following features: + +- Ability to add and edit all Core blocks. +- Familiar visual styles and main/sidebar layout. +- _Basic_ block persistence between page reloads. + +## Plugin setup and organization + +The custom editor is going to be built as a WordPress plugin. To keep things simple, the plugin will be named `Standalone Block Editor Demo` because that is what it does. + +The plugin file structure will look like this: + +![alt text](https://wordpress.org/gutenberg/files/2020/03/repo-files.png 'Screenshot showing file structure of the Plugin at https://github.com/getdave/standalone-block-editor.') + +Here is a brief summary of what's going on: + +- `plugin.php` – Standard plugin "entry" file with comment meta data, which requires `init.php`. +- `init.php` - Handles the initialization of the main plugin logic. +- `src/` (directory) - This is where the JavaScript and CSS source files will live. These files are _not_ directly enqueued by the plugin. +- `webpack.config.js` - A custom Webpack config extending the defaults provided by the [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) npm package to allow for custom CSS styles (via Sass). + +The only item not shown above is the `build/` directory, which is where the _compiled_ JS and CSS files are outputted by `@wordpress/scripts`. These files are enqueued by the plugin seperately. + +
      + Throughout this guide, filename references will be placed in a comment at the top of each code snippet so you can follow along. +
      + +With the basic file structure in place, let's look at what packages will be needed. + +## The "Core" of the editor + +While the WordPress Editor is comprised of many moving parts, at its core is the [`@wordpress/block-editor`](/packages/block-editor/README.md) package, which is best summarized by its own `README` file: + +> This module allows you to create and use standalone block editors. + +Perfect, this is the main package you will use to create the custom block editor instance. But first, you need to create a home for the editor. + +## Creating the custom "Block Editor" page + +Let's begin by creating a custom page within WordPress admin that will house the custom block editor instance. + +
      + If you're already comfortable with the process of creating custom admin pages in WordPress, you might want to skip ahead. +
      + +### Registering the page + +To do this, you need to [register a custom admin page](https://developer.wordpress.org/reference/functions/add_menu_page/) using the standard WordPress [`add_menu_page()`](https://developer.wordpress.org/reference/functions/add_menu_page/) helper: + +```php +// File: init.php + +add_menu_page( + 'Standalone Block Editor', // Visible page name + 'Block Editor', // Menu label + 'edit_posts', // Required capability + 'getdavesbe', // Hook/slug of page + 'getdave_sbe_render_block_editor', // Function to render the page + 'dashicons-welcome-widgets-menus' // Custom icon +); +``` + +The `getdave_sbe_render_block_editor` function will be used to render the contents of the admin page. As a reminder, the source code for each step is available in the [accompanying plugin](https://github.com/getdave/standalone-block-editor). + +### Adding the target HTML + +Since the block editor is a React-powered application, you need to output some HTML into the custom page where JavaScript can render the block editor. + +Let's use the `getdave_sbe_render_block_editor` function referenced in the step above. + +```php +// File: init.php + +function getdave_sbe_render_block_editor() { + ?> +
      + Loading Editor... +
      + ` component into the waiting `
      ` on the custom admin page. + +```jsx +domReady( function () { + const settings = window.getdaveSbeSettings || {}; + registerCoreBlocks(); + render( + , + document.getElementById( 'getdave-sbe-block-editor' ) + ); +} ); +``` + +
      + It is possible to render the editor from PHP without creating an unnecessary JS global. Check out the Edit Site package in the Gutenberg plugin for an example of this. +
      + +## Reviewing the `` component + +Let's take a closer look at the `` component that was used in the code above and lives in `src/editor.js` of the [companion plugin](https://github.com/getdave/standalone-block-editor). + +Despite its name, this is not the actual core of the block editor. Rather, it is a _wrapper_ component that will contain the components that form the custom editor's main body. + +### Dependencies + +The first thing to do inside `` is to pull in some dependencies. + +```jsx +// File: src/editor.js + +import Notices from 'components/notices'; +import Header from 'components/header'; +import Sidebar from 'components/sidebar'; +import BlockEditor from 'components/block-editor'; +``` + +The most important of these are the internal components `BlockEditor` and `Sidebar`, which will be covered shortly. + +The remaining components consist mostly of static elements that form the editor's layout and surrounding user interface (UI). These elements include the header and notice areas, among others. + +### Editor render + +With these components available, you can define the `` component. + +```jsx +// File: src/editor.js + +function Editor( { settings } ) { + return ( + + +
      + +
      + + +
      + +
      +
      + ); +} +``` + +In this process, the core of the editor's layout is being scaffolded, along with a few specialized [context providers](https://reactjs.org/docs/context.html#contextprovider) that make specific functionality available throughout the component hierarchy. + +Let's examine these in more detail: + +- `` – Enables the use of the ["Slot/Fill" + pattern](/docs/reference-guides/slotfills/README.md) through the component tree +- `` – Enables the use of [dropzones for drag and drop functionality](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/drop-zone) +- `` – Provides a "snack bar" Notice that will be rendered if any messages are dispatched to the `core/notices` store +- `
      ` – Renders the static title "Standalone Block Editor" at the top of the editor UI +- `` – The custom block editor component +- `` – Renders a slot into which ``s can be rendered + using the Slot/Fill mechanic + +### Keyboard navigation + +With this basic component structure in place, the only remaining thing left to do +is wrap everything in the [`navigateRegions` HOC](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/higher-order/navigate-regions) to provide keyboard navigation between the different "regions" in the layout. + +```jsx +// File: src/editor.js + +export default navigateRegions( Editor ); +``` + +## The custom `` + +Now the core layouts and components are in place. It's time to explore the custom implementation of the block editor itself. + +The component for this is called ``, and this is where the magic happens. + +Opening `src/components/block-editor/index.js` reveals that it's the most complex component encountered thus far. A lot going on, so start by focusing on what is being rendered by the `` component: + +```js +// File: src/components/block-editor/index.js + +return ( +
      + + + + +
      + + + + + + +
      +
      +
      +); +``` + +The key components are `` and ``. Let's examine these. + +### Understanding the `` component + +[``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider) is one of the most important components in the hierarchy. It establishes a new block editing context for a new block editor. + +As a result, it is _fundamental_ to the entire goal of this project. + +The children of `` comprise the UI for the block editor. These components then have access to data (via `Context`), enabling them to _render_ and _manage_ the blocks and their behaviors within the editor. + +```jsx +// File: src/components/block-editor/index.js + + +``` + +#### `BlockEditor` props + +You can see that `` accepts an array of (parsed) block objects as its `value` prop and, when there's a change detected within the editor, calls the `onChange` and/or `onInput` handler prop (passing the new Blocks as an argument). + +Internally it does this by subscribing to the provided `registry` (via the [`withRegistryProvider` HOC](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider/index.js#L158)), listening to block change events, determining whether the block changing was persistent, and then calling the appropriate `onChange|Input` handler accordingly. + +For the purposes of this simple project, these features allow you to: + +- Store the array of current blocks in state as `blocks`. +- Update the `blocks` state in memory on `onInput` by calling the hook setter + `updateBlocks(blocks)`. +- Handle basic persistence of blocks into `localStorage` using `onChange`. This is [fired when block updates are considered "committed"](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/provider#onchange). + +It's also worth recalling that the component accepts a `settings` property. This is where you will add the editor settings inlined earlier as JSON within `init.php`. You can use these settings to configure features such as custom colors, available image sizes, and [much more](https://github.com/WordPress/gutenberg/tree/4c472c3443513d070a50ba1e96f3a476861447b3/packages/block-editor#SETTINGS_DEFAULTS). + +### Understanding the `` component + +Alongside `` the next most interesting component is [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/index.js). + +This is one of the most important components as it's role is to **render a list of blocks into the editor**. + +It does this in part thanks to being placed as a child of ``, which affords it full access to all information about the state of the current blocks in the editor. + +#### How does `BlockList` work? + +Under the hood, `` relies on several other lower-level components in order to render the list of blocks. + +The hierarchy of these components can be _approximated_ as follows: + +```jsx +// Pseudo code for example purposes only. + + + /* renders a list of blocks from the rootClientId. */ + + /* renders a single block from the BlockList. */ + + /* renders the standard editable area of a block. */ + /* renders the block UI as defined by its `edit()` implementation. + */ + + + +``` + +Here is roughly how this works together to render the list of blocks: + +- `` loops over all the block `clientIds` and + renders each via [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/block.js). +- ``, in turn, renders the individual block + using its own subcomponent [``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/index.js). +- Finally, the [block itself](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/edit.js) is rendered using the `Component` placeholder component. + +The `@wordpress/block-editor` package components are among the most complex and involved. Understanding them is crucial if you want to grasp how the editor functions at a fundamental level. Studying these components is strongly advised. + +### Utility components in the custom block editor + +Jumping back to your custom `` component, it is also worth noting the following "utility" components: + +```js +// File: src/components/block-editor/index.js + +
      + /* 1. */ + + /* 2. */ + + /* 3. */ + + + +
      +``` + +These provide other important elements of functionality for the editor instance. + +1. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/keyboard-shortcuts/index.js) – Enables and usage of keyboard shortcuts within the editor +2. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/writing-flow/index.js) – Handles selection, focus management, and navigation across blocks +3. [``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/observe-typing)- Used to manage the editor's internal `isTyping` flag + +## Reviewing the sidebar + +Also within the render of the ``, is the `` component. + +```jsx +// File: src/components/block-editor/index.js + +return ( +
      + + /* <-- SIDEBAR */ + + +
      + // snip +
      +
      +
      +); +``` + +This is used, in part, to display advanced block settings via the `` component. + +```jsx + + + +``` + +However, the keen-eyed readers amongst you will have already noted the presence of a `` component within the `` (`src/editor.js`) component's +layout: + +```jsx +// File: src/editor.js + +
      + // <-- What's this? + + +``` + +Opening the `src/components/sidebar/index.js` file, you can see that this is, in fact, the component rendered within `` above. However, the implementation utilises +Slot/Fill to expose a `Fill` (``), which is subsequently imported and rendered inside of the `` component (see above). + +With this in place, you then can render `` as a child of the `Sidebar.InspectorFill`. This has the result of allowing you to keep `` within the React context of `` whilst allowing it to be rendered into the DOM in a separate location (i.e. in the ``). + +This might seem overly complex, but it is required in order for `` to have access to information about the current block. Without Slot/Fill, this setup would be extremely difficult to achieve. + +And with that you have covered the render of you custom ``. + +
      +<BlockInspector> +itself actually renders a Slot for <InspectorControls>. This is what allows you render a <InspectorControls>> component inside +the edit() definition for your block and have +it display within the editor's sidebar. Exploring this component in more detail is recommended. +
      + +## Block Persistence + +You have come a long way on your journey to create a custom block editor. But there is one major area left to touch upon - block persistence. In other words, having your +blocks saved and available _between_ page refreshes. + +![alt text](https://developer.wordpress.org/files/2023/07/custom-block-editor-persistance.gif 'Screencapture showing blocks being restored between page refreshes.') + +As this is only an _experiment_, this guide has opted to utilize the browser's `localStorage` API to handle saving block data. In a real-world scenario, you would likely choose a more reliable and robust system (e.g. a database). + +That said, let's take a closer look at how to handle save blocks. + +### Storing blocks in state + +Looking at the `src/components/block-editor/index.js` file, you will notice that some state has been created to store the blocks as an array: + +```jsx +// File: src/components/block-editor/index.js + +const [ blocks, updateBlocks ] = useState( [] ); +``` + +As mentioned earlier, `blocks` is passed to the "controlled" component `` as its `value` prop. This "hydrates" it with an initial set of blocks. Similarly, the `updateBlocks` setter is hooked up to the `onInput` callback on ``, which ensures that the block state is kept in sync with changes made to blocks within the editor. + +### Saving block data + +If you now turn your attention to the `onChange` handler, you will notice it is hooked up to a function `persistBlocks()` which is defined as follows: + +```js +// File: src/components/block-editor/index.js + +function persistBlocks( newBlocks ) { + updateBlocks( newBlocks ); + window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) ); +} +``` + +This function accepts an array of "committed" block changes and calls the state setter `updateBlocks`. It also stores the blocks within LocalStorage under the key `getdavesbeBlocks`. In order to achieve this, the block data is serialized into [Gutenberg "Block Grammar"](https://developer.wordpress.org/block-editor/principles/key-concepts/#blocks) format, meaning it can be safely stored as a string. + +If you open DeveloperTools and inspect the LocalStorage you will see serialized block data stored and updated as changes occur within the editor. Below is an example of the format: + +``` + +

      An experiment with a standalone Block Editor in the WordPress admin

      + + + +

      This is an experiment to discover how easy (or otherwise) it is to create a standalone instance of the Block Editor in the WordPress admin.

      + +``` + +### Retrieving previous block data + +Having persistence in place is all well and good, but it's only useful if that data is retrieved and _restored_ within the editor upon each full page reload. + +Accessing data is a side effect, so you must use the `useEffect` hook to handle this. + +```jsx +// File: src/components/block-editor/index.js + +useEffect( () => { + const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' ); + + if ( storedBlocks && storedBlocks.length ) { + updateBlocks( () => parse( storedBlocks ) ); + createInfoNotice( 'Blocks loaded', { + type: 'snackbar', + isDismissible: true, + } ); + } +}, [] ); +``` + +This handler: + +- Grabs the serialized block data from local storage. +- Converts the serialized blocks back to JavaScript objects using the `parse()` utility. +- Calls the state setter `updateBlocks` causing the `blocks` value to be updated in state to reflect the blocks retrieved from LocalStorage. + +As a result of these operations, the controlled `` component is updated with the blocks restored from LocalStorage, causing the editor to show these blocks. + +Finally, you will want to generate a notice - which will display in the `` component as a "snackbar" notice - to indicate that the blocks have been restored. + +## Wrapping up + +Congratulations for completing this guide. You should now have a better understanding of how the block editor works under the hood. + +The full code for the custom block editor you have just built is [available on GitHub](https://github.com/getdave/standalone-block-editor). Download and try it out for yourself. Experiment, then and take things even further. diff --git a/docs/how-to-guides/platform/custom-block-editor/README.md b/docs/how-to-guides/platform/custom-block-editor/README.md deleted file mode 100644 index b21907577c150e..00000000000000 --- a/docs/how-to-guides/platform/custom-block-editor/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Building a custom block editor - -The purpose of [this tutorial](/docs/how-to-guides/platform/custom-block-editor/tutorial.md) is to step through the fundamentals of creating a custom instance of a "block editor". - -![alt text](https://wordpress.org/gutenberg/files/2020/03/editor.png 'The Standalone Editor instance populated with example Blocks within a custom WP Admin page.') - -The editor you will see in this tutorial (as above) is **_not_ the same Block Editor you are familiar with when creating Posts** in with WordPress. Rather it is an entirely **custom block editor instance** built using the lower-level [`@wordpress/block-editor`](/packages/block-editor/README.md) package (and friends). - -## Following this tutorial - -To follow along with this tutorial, you can [download the accompanying WordPress plugin](https://github.com/getdave/standalone-block-editor) which includes all of the examples for you to try on your own site. - -## Code Syntax - -Code snippets are provided using JSX syntax. Note it is not required to use JSX to create blocks or extend the editor, you can use plain JavaScript. However, once familiar with JSX, many developers tend find it is easier to read and write, thus most code examples you'll find use that syntax. - -- [Start custom block editor tutorial](/docs/how-to-guides/platform/custom-block-editor/tutorial.md) diff --git a/docs/how-to-guides/platform/custom-block-editor/tutorial.md b/docs/how-to-guides/platform/custom-block-editor/tutorial.md deleted file mode 100644 index 1e49433da3e654..00000000000000 --- a/docs/how-to-guides/platform/custom-block-editor/tutorial.md +++ /dev/null @@ -1,623 +0,0 @@ -# Tutorial: building a custom block editor - -This tutorial will step through the fundamentals of creating a custom instance -of a "block editor" using the `@wordpress/block-editor` package. - -## Table of Contents - -- [Introduction](#introduction). -- [What we're going to be building](#what-were-going-to-be-building). -- [Plugin setup and organization](#plugin-setup-and-organization). -- [The "Core" of the Editor](#the-core-of-the-editor). -- [Creating the custom "Block Editor" page in WP Admin](#creating-the-custom-block-editor-page-in-wp-admin). -- [Registering and Rendering our custom block editor](#registering-and-rendering-our-custom-block-editor). -- [Reviewing the `` component](#reviewing-the-editor-component). -- [The custom ``](#the-custom-blockeditor). -- [Reviewing the Sidebar](#reviewing-the-sidebar). -- [Block Persistence](#block-persistence). -- [Wrapping up](#wrapping-up). - -## Introduction - -The Gutenberg codebase is complex, with many packages and components, but at its core it is a tool for managing and editing blocks. Therefore, when working on the editor it is important to gain a better understanding of how block editing works at a _fundamental_ level. - -To do this, this tutorial will walk you through building a **fully functioning, _custom_ block editor "instance"** within WordPress, introducing you to the key packages and components along the way. - -By the end of this article, you should have gained a good understanding of how the block editor works and some of the knowledge required to put together your own block editor instances. - -## What we're going to be building - -We're going to be creating an (almost) fully functioning Block Editor instance. - -![The Standalone Editor instance populated with example Blocks within a custom WP Admin page](https://wordpress.org/gutenberg/files/2020/03/editor.png) - -This block editor will not be the same _Block Editor_ you are familiar with when creating `Post`s in WP Admin. Rather it will be an entirely custom instance which will live within a custom WP Admin page called (imaginatively) "Block Editor". - -Our editor will have the following features: - -- Ability to add and edit all Core Blocks. -- Familiar visual styles and main/sidebar layout. -- _Basic_ block persistence between page reloads. - -With that in mind, let's start taking our first steps towards building this. - -## Plugin setup and organization - -Our custom editor is going to be built as a WordPress Plugin. To keep things simple. we'll call this `Standalone Block Editor Demo` because that is what it does. Nice! - -Let's take a look at our Plugin file structure: - -![alt text](https://wordpress.org/gutenberg/files/2020/03/repo-files.png 'Screenshot showing file structure of the Plugin at https://github.com/getdave/standalone-block-editor.') - -Here's a brief summary of what's going on: - -- `plugin.php` - standard Plugin "entry" file with comment meta data. Requires `init.php`. -- `init.php` - handles the initialization of the main Plugin logic. We'll be spending a lot of time here. -- `src/` (directory) - this is where our JavaScript (and CSS) source files will live. These files are _not_ directly enqueued by the Plugin. -- `webpack.config.js` - a custom Webpack config extending the defaults provided by the `@wordpress/scripts` npm package to allow for custom CSS styles (via Sass). - -The only item not shown above is the `build/` directory, which is where our _compiled_ JS and CSS files will be outputted by `@wordpress/scripts` ready to be enqueued by our Plugin. - -**Note:** throughout this tutorial, filename references will be placed in a comment at the top of each code snippet so you can follow along. - -With our basic file structure in place, we can now start looking at what package we're going to need. - -## The "Core" of the Editor - -Whilst the Gutenberg Editor is comprised of many moving parts, at it's core is the `@wordpress/block-editor` package. - -It's role is perhaps best summarized by its own `README` file: - -> This module allows you to create and use standalone block editors. - -This is great and exactly what we need! Indeed, it is the main package we'll be using to create our custom block editor instance. - -However, before we can get to working with this package in code, we're going to need to create a home for our editor within WP Admin. - -## Creating the custom "Block Editor" page in WP Admin - -As a first step, we need to create a custom page within WP Admin. - -**Note**: if you're already comfortable with the process of creating custom Admin pages in WordPress you might want to [skip ahead](#registering-and-rendering-our-custom-block-editor). - -### Registering the Page - -To do this we [register our custom admin page](https://developer.wordpress.org/reference/functions/add_menu_page/) using the standard WP `add_menu_page()` helper: - -```php -// init.php - -add_menu_page( - 'Standalone Block Editor', // visible page name - 'Block Editor', // menu label - 'edit_posts', // required capability - 'getdavesbe', // hook/slug of page - 'getdave_sbe_render_block_editor', // function to render the page - 'dashicons-welcome-widgets-menus' // custom icon -); -``` - -Note the reference to a function `getdave_sbe_render_block_editor` which is the function which we will use to render the contents of the admin page. - -### Adding the target HTML - -As the block editor is a React powered application, we now need to output some HTML into our custom page into which the JavaScript can render the block editor. - -To do this we need to look at our `getdave_sbe_render_block_editor` function referenced in the step above. - -```php -// init.php - -function getdave_sbe_render_block_editor() { - ?> -
      - Loading Editor... -
      - ` component into the waiting `
      ` on our custom Admin page. - -```jsx -domReady( function () { - const settings = window.getdaveSbeSettings || {}; - registerCoreBlocks(); - render( - , - document.getElementById( 'getdave-sbe-block-editor' ) - ); -} ); -``` - -**Note**: it is possible to render the editor from PHP without creating an unnecessary JS global. Check out [the Edit Site package in Gutenberg Core for an example of this](https://href.li/?https://github.com/WordPress/gutenberg/blob/c6821d7e64a54eb322583a35daedc6c192ece850/lib/edit-site-page.php#L135). - -## Reviewing the `` component - -Let's take a closer look at the `` component we saw being used above. - -Despite its name, this _is not_ the actual core of the block editor. Rather it is a _wrapper_ component we've created to contain the components which form the main body of our custom editor. - -### Dependencies - -The first thing we do inside `` is to pull in some dependencies. - -```jsx -// src/editor.js - -import Notices from 'components/notices'; -import Header from 'components/header'; -import Sidebar from 'components/sidebar'; -import BlockEditor from 'components/block-editor'; -``` - -The most important of these are the internal components `BlockEditor` and `Sidebar`, which we will explore in greater detail shortly. - -The remaining components are largely static elements which form the layout and surrounding UI of the editor (eg: header and notice areas). - -### Editor Render - -With these components available we can proceed to define our `` component. - -```jsx -// src/editor.js - -function Editor( { settings } ) { - return ( - - -
      - -
      - - -
      - -
      -
      - ); -} -``` - -Here we are scaffolding the core of the editor's layout alongside a few specialised [context providers](https://reactjs.org/docs/context.html#contextprovider) which make particular functionality available throughout the component hierarchy. - -Let's examine these in more detail: - -- `` - enables the use of the ["Slot/Fill" - pattern](/docs/reference-guides/slotfills/README.md) through our component tree. -- `` - enables the use of [dropzones for drag and drop functionality](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/drop-zone). -- `` - custom component. Provides a "snack bar" Notice that will be rendered if any messages are dispatched to `core/notices` store. -- `
      ` - renders the static title "Standalone Block Editor" at the top of the - editor UI. -- `` - our custom block editor component. This is where things get - interesting. We'll focus a little more on this in a moment. -- `` - renders a slot into which ``s can be rendered - using the Slot/Fill mechanic. - -### Keyboard Navigation - -With this basic component structure in place the only remaining thing left to do -is wrap everything in [the `navigateRegions` HOC](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/higher-order/navigate-regions) to provide keyboard navigation between the different "regions" in the layout. - -```jsx -// src/editor.js - -export default navigateRegions( Editor ); -``` - -## The custom `` - -Now we have a our core layouts and components in place, it's time to explore our -custom implementation of the block editor itself. - -The component for this is called `` and this is where the magic happens. - -Opening `src/components/block-editor/index.js` we see that this is the most -complex of the components we have encountered thus far. - -There's a lot going on so let's break this down! - -### Understanding the render - -To start, let's focus on what is being rendered by the `` component: - -```js -// src/components/block-editor/index.js - -return ( -
      - - - - -
      - - - - - - -
      -
      -
      -); -``` - -The key components to focus on here are `` and ``. Let's examine these. - -### Understanding the `` component - -[``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider) is one of the most important components in the hierarchy. As we learnt earlier, it establishes a new block editing context for a new block editor. - -As a result, it is _fundamental_ to the entire goal of our project. - -The children of `` comprise the UI for the block -editor. These components then have access to data (via `Context`) which enables -them to _render_ and _manage_ the Blocks and their behaviors within the editor. - -```jsx -// src/components/block-editor/index.js - - -``` - -#### `BlockEditor` props - -We can see that `` accepts array of (parsed) block objects as its `value` prop and, when there's a change detected within the editor, calls the `onChange` and/or `onInput` handler prop (passing the new Blocks as a argument). - -Internally it does this by subscribing to the provided `registry` (via the [`withRegistryProvider` HOC](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider/index.js#L158)), listening to block change events, determining whether Block changing was persistent, and then calling the appropriate `onChange|Input` handler accordingly. - -For the purposes of our simple project these features allow us to: - -- Store the array of current blocks in state as `blocks`. -- Update the `blocks` state in memory on `onInput` by calling the hook setter - `updateBlocks(blocks)`. -- Handle basic persistence of blocks into `localStorage` using `onChange`. This is [fired when block updates are considered - "committed"](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/provider#onchange). - -It's also worth recalling that the component accepts a `settings` prop. This accepts the editor settings which we inlined as JSON within `init.php` earlier. This configures features such as custom colors, available image sizes and [much more](https://github.com/WordPress/gutenberg/tree/4c472c3443513d070a50ba1e96f3a476861447b3/packages/block-editor#SETTINGS_DEFAULTS). - -### Understanding the `` component - -Alongside `` the next most interesting component is [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/index.js). - -This is one of the most important components as it's role is to **render a list of Blocks into the editor**. - -It does this in part thanks to being placed as a child of `` which affords it full access to all information about the state of the current Blocks in the editor. - -#### How does `BlockList` work? - -Under the hood `` relies on several other lower-level components in order to render the list of Blocks. - -The hierarchy of these components can be _approximated_ as follows: - -```jsx -// Pseudo code - example purposes only - - - /* renders a list of Blocks from the rootClientId. */ - - /* renders a single "Block" from the BlockList. */ - - /* renders the standard editable area of a Block. */ - /* renders the Block UI as defined by its `edit()` implementation. - */ - - - -``` - -Here's roughly how this works together to render our list of blocks: - -- `` loops over all the Block clientIds and - renders each via [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/block.js). -- `` in turn renders the individual "Block" - via it's own subcomponent [``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/index.js). -- Finally [the Block itself](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/edit.js) is rendered using the `Component` placeholder component. - -These are some of the most complex and involved components within the `@wordpress/block-editor` package. That said, if you want to have a strong grasp of how the editor works at a fundamental level, I strongly advise making a study of these components. I leave this as an exercise for the reader! - -### Utility components in our custom block editor - -Jumping back to our own custom `` component, it is also worth noting the following "utility" components: - -```js -// src/components/block-editor/index.js - -
      - /* 1. */ - - /* 2. */ - - /* 3. */ - - - -
      -``` - -These provide other important elements of functionality for our editor instance. - -1. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/keyboard-shortcuts/index.js) - enables and usage of keyboard shortcuts within the editor. -2. [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/writing-flow/index.js) - handles selection, focus management and navigation across blocks. -3. [``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/observe-typing)- used to manage the editor's internal `isTyping` flag. This is used in various places, most commonly to show/hide the Block toolbar in response to typing. - -## Reviewing the Sidebar - -Also within the render of our ``, is our `` component. - -```jsx -// src/components/block-editor/index.js - -return ( -
      - - /* <-- SIDEBAR */ - - -
      - // snip -
      -
      -
      -); -``` - -This is used - alongside other things - to display advanced Block settings via the `` component. - -```jsx - - - -``` - -However, the keen-eyed readers amongst you will have already noted the presence -of a `` component within our `` (`src/editor.js`) component's -layout: - -```jsx -// src/editor.js - -
      - // <-- eh!? - - -``` - -Opening `src/components/sidebar/index.js` we see that this is in fact the -component rendered within `` above. However, the implementation utilises -Slot/Fill to expose a `Fill` (``) which we subsequently -`import` and render inside of our `` component (see above). - -With this in place, we then render `` as a child of the -`Sidebar.InspectorFill`. This has the result of allowing us to keep -`` within the React context of `` whilst -allowing it to be rendered into the DOM in a separate location (ie: in the ``). - -This might seem overly complex, but it is required in order that -`` can have access to information about the current Block. -Without Slot/Fill this setup would be extremely difficult to achieve. - -Aside: -[``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-inspector/index.js) -itself actually renders a `Slot` for [``](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inspector-controls). This is what allows you [render a `` component inside -the `edit()` definition for your block](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-library/src/paragraph/edit.js#L127) and have -it display within Gutenberg's sidebar. I recommend looking into this component -in more detail. - -And with that we have covered the render of our custom ``! - -## Block Persistence - -We've come a long way on our journey to create a custom block editor. But there is -one major area left to touch upon - Block persistence; that is the act of having our -Blocks saved and **available _between_ page refreshes**. - -![alt text](https://wordpress.org/gutenberg/files/2020/03/block-persistance.gif 'Screencapture showing added Blocks being restored between page refreshes.') - -As this is only an _experiment_ we've opted to utilise the browser's -`localStorage` API to handle saving Block data. In a real-world scenario however -you'd like choose a more reliable and robust system (eg: a database). - -That said, let's take a closer look at how we're handling saving our Blocks. - -### Storing blocks in state - -Opening `src/components/block-editor/index.js` we will notice we have created -some state to store our Blocks as an array: - -```jsx -// src/components/block-editor/index.js - -const [ blocks, updateBlocks ] = useState( [] ); -``` - -As mentioned earlier, `blocks` is passed to the "controlled" component `` as its `value` prop. This "hydrates" it with an initial set of Blocks. Similarly, the `updateBlocks` setter is hooked up to the `onInput` callback on `` which ensures that our block state is kept in sync with changes made to blocks within the editor. - -### Saving Block data - -If we now turn our attention to the `onChange` handler, we will notice it is -hooked up to a function `persistBlocks()` which is defined as follows: - -```js -// src/components/block-editor/index.js - -function persistBlocks( newBlocks ) { - updateBlocks( newBlocks ); - window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) ); -} -``` - -This function accepts an array of "committed" block changes and calls the state -setter `updateBlocks`. In addition to this however, it also stores the blocks -within LocalStorage under the key `getdavesbeBlocks`. In order to achieve this -the Block data is serialized into [Gutenberg "Block Grammar"](https://developer.wordpress.org/block-editor/principles/key-concepts/#blocks) format, meaning it can be safely stored as a string. - -If we open DeveloperTools and inspect our LocalStorage we will see serialized -Block data stored and updated as changes occur within the editor. Below is an -example of the format: - -``` - -

      An experiment with a standalone Block Editor in WPAdmin

      - - - -

      This is an experiment to discover how easy (or otherwise) it is to create a standalone instance of the Block Editor in WPAdmin.

      - -``` - -### Retrieving previous block data - -Having persistence in place is all well and good, but it's useless unless that -data is retrieved and _restored_ within the editor upon each full page reload. - -Accessing data is a side effect, so naturally we reach for our old (new!?) -friend the `useEffect` hook to handle this. - -```jsx -// src/components/block-editor/index.js - -useEffect( () => { - const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' ); - - if ( storedBlocks && storedBlocks.length ) { - updateBlocks( () => parse( storedBlocks ) ); - createInfoNotice( 'Blocks loaded', { - type: 'snackbar', - isDismissible: true, - } ); - } -}, [] ); -``` - -In this handler, we: - -- Grab the serialized block data from local storage. -- Convert the serialized blocks back to JavaScript objects using the `parse()` - utility. -- Call the state setter `updateBlocks` causing the `blocks` value to be updated - in state to reflect the blocks retrieved from LocalStorage. - -As a result of these operations the controlled `` component -is updated with the blocks restored from LocalStorage causing the editor to -show these blocks. - -Finally, for good measure we generate a notice - which will display in our `` component as a "snackbar" notice - to indicate that the blocks have been restored. - -## Wrapping up - -If you've made it this far then congratulations! I hope you now have a better understanding of how the block editor works under the hood. - -In addition, you've reviewed an working example of the code required to implement your own custom functioning block editor. This information should prove useful, especially as Gutenberg expands beyond editing just the `Post` and into Widgets, Full Site Editing and beyond! - -The full code for the custom functioning block editor we've just built is [available on GitHub](https://github.com/getdave/standalone-block-editor). I encourage you to download and try it out for yourself. Experiment, then and take things even further! diff --git a/docs/manifest.json b/docs/manifest.json index 5f75e49924f355..578d4762224763 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -170,15 +170,9 @@ { "title": "Building a custom block editor", "slug": "custom-block-editor", - "markdown_source": "../docs/how-to-guides/platform/custom-block-editor/README.md", + "markdown_source": "../docs/how-to-guides/platform/custom-block-editor.md", "parent": "platform" }, - { - "title": "Tutorial: building a custom block editor", - "slug": "tutorial", - "markdown_source": "../docs/how-to-guides/platform/custom-block-editor/tutorial.md", - "parent": "custom-block-editor" - }, { "title": "Create your First App with Gutenberg Data", "slug": "data-basics", diff --git a/docs/toc.json b/docs/toc.json index 1660afdcc29497..b523fcedea416a 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -80,11 +80,7 @@ { "docs/how-to-guides/platform/README.md": [ { - "docs/how-to-guides/platform/custom-block-editor/README.md": [ - { - "docs/how-to-guides/platform/custom-block-editor/tutorial.md": [] - } - ] + "docs/how-to-guides/platform/custom-block-editor.md": [] } ] }, From 1f1bcfb1fd63730526eaa3399d8de4e245b7f96c Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 3 Aug 2023 09:31:40 -0400 Subject: [PATCH 057/150] refactor: Reduce server log verbosity (#53268) * refactor: Remove unsupported line height Aztec warning While arguably educational, this warning increases the amount of noise within the server log, which can make it difficult to debug errors, warnings, or intentionally logged values. * refactor: Remove Hermes engine status log While informative, this un-filterable log increases the amount of noise in the server log, which can make it difficult to debug errors, warnings, or intentionally logged values. Additionally, the Hermes engine is now globally enabled for both iOS and Android. * refactor: Remove outdated LogBox configuration for LayoutAnimation This warning appears to no longer occur, presumably thanks to: https://github.com/WordPress/gutenberg/pull/52563 * refactor: Remove outdated LogBox configuration for gesture handler This warning appears to no longer occur. The configuration was originally added in: a1b11c512ce5fa8f5eb59dcd4a27b3b52b224ec1 --- packages/react-native-aztec/src/AztecView.js | 3 --- packages/react-native-editor/src/setup.js | 19 ------------------- .../src/test/index.test.js | 1 - 3 files changed, 23 deletions(-) diff --git a/packages/react-native-aztec/src/AztecView.js b/packages/react-native-aztec/src/AztecView.js index e05737f5fb0f99..5fb2d4f5fd91b4 100644 --- a/packages/react-native-aztec/src/AztecView.js +++ b/packages/react-native-aztec/src/AztecView.js @@ -291,9 +291,6 @@ class AztecView extends Component { if ( style.hasOwnProperty( 'lineHeight' ) ) { delete style.lineHeight; - window.console.warn( - "Removing lineHeight style as it's not supported by native AztecView" - ); // Prevents passing line-height within styles to avoid a crash due to values without units // We now support this but passing line-height as a prop instead. } diff --git a/packages/react-native-editor/src/setup.js b/packages/react-native-editor/src/setup.js index b09c404e9f0ab7..f5c80883b4b6d7 100644 --- a/packages/react-native-editor/src/setup.js +++ b/packages/react-native-editor/src/setup.js @@ -22,21 +22,6 @@ import setupApiFetch from './api-fetch-setup'; const reactNativeSetup = () => { LogBox.ignoreLogs( [ 'Require cycle:', // TODO: Refactor to remove require cycles - 'lineHeight', // TODO: Remove lineHeight warning from Aztec - /** - * TODO: Migrate to @gorhom/bottom-sheet or replace usage of - * LayoutAnimation to Animated. KeyboardAvoidingView's usage of - * LayoutAnimation collides with both BottomSheet and NavigationContainer - * usage of LayoutAnimation simultaneously https://github.com/facebook/react-native/issues/12663, - * https://github.com/facebook/react-native/issues/10606 - */ - 'Overriding previous layout animation', - ] ); - - // "@react-navigation" package uses the old API of gesture handler, - // so the warning will be silenced until it gets updated. - LogBox.ignoreLogs( [ - "[react-native-gesture-handler] Seems like you're using an old API with gesture components, check out new Gestures system!", ] ); I18nManager.forceRTL( false ); // Change to `true` to debug RTL layout easily. @@ -50,10 +35,6 @@ const gutenbergSetup = () => { setupApiFetch(); - const isHermes = () => global.HermesInternal !== null; - // eslint-disable-next-line no-console - console.log( 'Hermes is: ' + isHermes() ); - setupInitHooks(); }; diff --git a/packages/react-native-editor/src/test/index.test.js b/packages/react-native-editor/src/test/index.test.js index 418a3e8b55c752..e64e5ff934b202 100644 --- a/packages/react-native-editor/src/test/index.test.js +++ b/packages/react-native-editor/src/test/index.test.js @@ -197,7 +197,6 @@ describe( 'Register Gutenberg', () => { registerCoreBlocksCallOrder ); expect( callbackCallOrder ).toBeLessThan( onRenderEditorCallOrder ); - expect( console ).toHaveLoggedWith( 'Hermes is: true' ); initializeEditorMock.mockRestore(); } ); From af0beae28b2e4d6e0463005dae87d91b84c4e819 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Thu, 3 Aug 2023 17:06:33 +0200 Subject: [PATCH 058/150] Mobile Release v1.100.2 (#53293) * Release script: Update react-native-editor version to 1.100.1 * Release script: Update with changes from 'npm run core preios' * Update `react-native-editor` changelog * Release script: Update react-native-editor version to 1.100.2 * Release script: Update with changes from 'npm run core preios' * Mobile - Fix iOS Focus loop for RichText components (#53217) * Mobile - Update changelog * fix: Avoid iOS block appender focus loop The focus callback triggered by Aztec-based programmatic focus events can result in focus loops between rich text elements. Android: This intentional no-op function prevents focus loops originating when the native Aztec module programmatically focuses the instance. The no-op is explicitly passed as an `onFocus` prop to avoid future prop spreading from inadvertently introducing focus loops. The user-facing focus of the element is handled by `onPress` instead. See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/302 iOS: Programmatic focus from the native Aztec module is required to ensure the React-based `TextStateInput` ref is properly set when focus is *returned* to an instance, e.g. dismissing a bottom sheet. If the ref is not updated, attempts to dismiss the keyboard via the `ToolbarButton` will fail. See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/702 The Android keyboard is, likely erroneously, already dismissed in the contexts where programmatic focus may be required on iOS. - https://github.com/WordPress/gutenberg/issues/28748 - https://github.com/WordPress/gutenberg/issues/29048 - https://github.com/wordpress-mobile/WordPress-Android/issues/16167 Programmatic swapping focus from element to another often leads to focus loops, only delegate the programmatic focus if there are no elements focused. See: https://github.com/wordpress-mobile/WordPress-iOS/issues/18783 * fix: Programmatic Aztec input focus only updates internal ref Programmatically swapping input focus creates an infinite loop if the user taps a different input in between the programmatic focus and the resulting update to the React Native TextInputState focused element ref. To mitigate this, the Aztec now updates the focused element ref, but does not call the native focus methods. See: https://github.com/wordpress-mobile/WordPress-iOS/issues/18783 * Mobile - AztecView - Check for isFocused before forcing the focus * Mobile - DefaultBlockAppender and BlockList Footer placeholders - Removes inline functions and other minor code style changes * Mobile - AztecView - Trigger _onFocus within _onAztecFocus to prevent having a RichText component focused while another block is selected --------- Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --------- Co-authored-by: Derek Blank Co-authored-by: Carlos Garcia Co-authored-by: David Calhoun <438664+dcalhoun@users.noreply.github.com> --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 3 +++ packages/react-native-editor/ios/Podfile.lock | 20 +++++++++---------- packages/react-native-editor/package.json | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 2eec58c75a82c6..ebc7a6b76caffd 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.100.1", + "version": "1.100.2", "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 f84542655d4b9f..70ebc5f22846c0 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.100.1", + "version": "1.100.2", "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 de65021b44d752..aa5274fe249004 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -15,6 +15,9 @@ For each user feature we should also add a importance categorization label to i - [**] Upgrade React Native to 0.71.11 [#51303] - [*] Upgrade Gradle to 8.2.1 & AGP to 8.1.0 [#52872] - [*] Fix Gallery block selection when adding media [#53127] + +## 1.100.2 +======= - [**] Fix iOS Focus loop for RichText components [#53217] ## 1.100.1 diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index d55f7f407fe73c..1f748c877f23bb 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.71.11) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.100.1): + - Gutenberg (1.100.2): - React-Core (= 0.71.11) - React-CoreModules (= 0.71.11) - React-RCTImage (= 0.71.11) @@ -394,7 +394,7 @@ PODS: - React-RCTImage - RNSVG (13.9.0): - React-Core - - RNTAztecView (1.100.1): + - RNTAztecView (1.100.2): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - SDWebImage (5.11.1): @@ -577,7 +577,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: f07662560742d82a5b73cee116c70b0b49bcc220 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - Gutenberg: 68bee259a516c052424d03936ddd961cf8b906cb + Gutenberg: d727d9e158d57edf635d6851c3ed6780a60ecc2b libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: f6187ec763637e6a57f5728dd9a3bdabc6d6b4e0 @@ -593,13 +593,13 @@ SPEC CHECKSUMS: React-jsiexecutor: 089cd07c76ecf498960a64ba8ae0f2dddd382f44 React-jsinspector: b6ed4cb3ffa27a041cd440300503dc512b761450 React-logger: 186dd536128ae5924bc38ed70932c00aa740cd5b - react-native-blur: 8cd9b4a8007166ad643f4dff914c3fddd2ff5b9a + react-native-blur: 3e9c8e8e9f7d17fa1b94e1a0ae9fd816675f5382 react-native-get-random-values: b6fb85e7169b9822976793e467458c151c3e8b69 react-native-safe-area: c9cf765aa2dd96159476a99633e7d462ce5bb94f - react-native-safe-area-context: 5496bfc1fb0f0e096eeb740377d6a3d62597abe2 + react-native-safe-area-context: 36cc67648134e89465663b8172336a19eeda493d react-native-slider: dff0d8a46f368a8d1bacd8638570d75b9b0be400 - react-native-video: afb806880af4f6612683ab678a793ae41bc39705 - react-native-webview: e3b659a6d614bb37fb12a2de82c91a378c59d84b + react-native-video: 6dee623307ed9d04d1be2de87494f9a0fa2041d1 + react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1 React-perflogger: e706562ab7eb8eb590aa83a224d26fa13963d7f2 React-RCTActionSheet: 57d4bd98122f557479a3359ad5dad8e109e20c5a React-RCTAnimation: ccf3ef00101ea74bda73a045d79a658b36728a60 @@ -613,14 +613,14 @@ SPEC CHECKSUMS: React-RCTVibration: f09f08de63e4122deb32506e20ca4cae6e4e14c1 React-runtimeexecutor: 4817d63dbc9d658f8dc0ec56bd9b83ce531129f0 ReactCommon: e2d70ebcd90a2eaab343fb0cc23bbdb5ac321f5c - RNCClipboard: 33ec5830d149587a7d9de7bee1f80889f101dda6 - RNCMaskedView: 627993e2ddd1ed09c1d540a61c4311cfc507bbe7 + RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc + RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6 RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7 RNGestureHandler: f75d81410b40aaa99e71ae8f8bb7a88620c95042 RNReanimated: df2567658c01135f9ff4709d372675bcb9fd1d83 RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789 RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 - RNTAztecView: f959829b0cfd6b660f5a0cc6b92bf60ba96c0ab7 + RNTAztecView: 769d2a4bc43758cf2977cdc429cdd4b1b79230bc SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 53339f56fb807b..00fabf738ec4d1 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.100.1", + "version": "1.100.2", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 8a29e63a34c57b2a5e5126a6dbbc7a9502e407c9 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 3 Aug 2023 10:06:59 -0600 Subject: [PATCH 059/150] Image block: Fix stretched images constrained by max-width (#53274) * Fix dragging to resize from stretching image in the frontend * Add image block v7 deprecation * Update deprecation comment * Prevent image losing its aspect ratio at smaller window dimensions * Revert "Prevent image losing its aspect ratio at smaller window dimensions" This reverts commit 8ac9f8c4469d5e6cdfd5aa06bc0c89a37771f7c5. --------- Co-authored-by: scruffian --- packages/block-library/src/image/block.json | 4 +- .../block-library/src/image/deprecated.js | 209 +++++++++++++++++- packages/block-library/src/image/image.js | 52 +++-- packages/block-library/src/image/save.js | 2 - ...ed-v2-add-is-resized-class.serialized.html | 2 +- ...cated-v3-add-align-wrapper.serialized.html | 2 +- ...ed-v4-remove-align-wrapper.serialized.html | 2 +- ...-v6-add-style-width-height.serialized.html | 2 +- ...ted-v7-string-width-height-attributes.html | 3 + ...ted-v7-string-width-height-attributes.json | 15 ++ ...string-width-height-attributes.parsed.json | 15 ++ ...ng-width-height-attributes.serialized.html | 3 + 12 files changed, 278 insertions(+), 33 deletions(-) create mode 100644 test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.html create mode 100644 test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.json create mode 100644 test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.parsed.json create mode 100644 test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.serialized.html diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 4d044d221ec704..005f322722d701 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -64,10 +64,10 @@ "__experimentalRole": "content" }, "width": { - "type": "number" + "type": "string" }, "height": { - "type": "number" + "type": "string" }, "aspectRatio": { "type": "string" diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index edbabd7fb2d835..d159009b173739 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -740,4 +740,211 @@ const v6 = { }, }; -export default [ v6, v5, v4, v3, v2, v1 ]; +/** + * Deprecation for converting to string width and height block attributes and + * removing the width and height img element attributes which are not needed + * as they get added by the TODO hook. + * + * @see https://github.com/WordPress/gutenberg/pull/53274 + */ +const v7 = { + attributes: { + align: { + type: 'string', + }, + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + __experimentalRole: 'content', + }, + alt: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + __experimentalRole: 'content', + }, + caption: { + type: 'string', + source: 'html', + selector: 'figcaption', + __experimentalRole: 'content', + }, + title: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'title', + __experimentalRole: 'content', + }, + href: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'href', + __experimentalRole: 'content', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'rel', + }, + linkClass: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'class', + }, + id: { + type: 'number', + __experimentalRole: 'content', + }, + width: { + type: 'number', + }, + height: { + type: 'number', + }, + aspectRatio: { + type: 'string', + }, + scale: { + type: 'string', + }, + sizeSlug: { + type: 'string', + }, + linkDestination: { + type: 'string', + }, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'target', + }, + }, + supports: { + anchor: true, + behaviors: { + lightbox: true, + }, + color: { + text: false, + background: false, + }, + filter: { + duotone: true, + }, + __experimentalBorder: { + color: true, + radius: true, + width: true, + __experimentalSkipSerialization: true, + __experimentalDefaultControls: { + color: true, + radius: true, + width: true, + }, + }, + }, + migrate( { width, height, ...attributes } ) { + return { + ...attributes, + width: `${ width }px`, + height: `${ height }px`, + }; + }, + save( { attributes } ) { + const { + url, + alt, + caption, + align, + href, + rel, + linkClass, + width, + height, + aspectRatio, + scale, + id, + linkTarget, + sizeSlug, + title, + } = attributes; + + const newRel = ! rel ? undefined : rel; + const borderProps = getBorderClassesAndStyles( attributes ); + + const classes = classnames( { + [ `align${ align }` ]: align, + [ `size-${ sizeSlug }` ]: sizeSlug, + 'is-resized': width || height, + 'has-custom-border': + !! borderProps.className || + ( borderProps.style && + Object.keys( borderProps.style ).length > 0 ), + } ); + + const imageClasses = classnames( borderProps.className, { + [ `wp-image-${ id }` ]: !! id, + } ); + + const image = ( + { + ); + + const figure = ( + <> + { href ? ( + + { image } + + ) : ( + image + ) } + { ! RichText.isEmpty( caption ) && ( + + ) } + + ); + + return ( +
      + { figure } +
      + ); + }, +}; + +export default [ v7, v6, v5, v4, v3, v2, v1 ]; diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 5fe5c93f4199b4..a880c20ed59ab9 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -114,6 +114,11 @@ export default function Image( { linkTarget, sizeSlug, } = attributes; + + // The only supported unit is px, so we can parseInt to strip the px here. + const numericWidth = width ? parseInt( width, 10 ) : undefined; + const numericHeight = height ? parseInt( height, 10 ) : undefined; + const imageRef = useRef(); const prevCaption = usePrevious( caption ); const [ showCaption, setShowCaption ] = useState( !! caption ); @@ -473,23 +478,14 @@ export default function Image( { ) } { // Rebuilding the object forces setting `undefined` // for values that are removed since setAttributes // doesn't do anything with keys that aren't set. setAttributes( { - width: - newValue.width && - parseInt( newValue.width, 10 ), - height: - newValue.height && - parseInt( newValue.height, 10 ), + width: newValue.width, + height: newValue.height, scale: newValue.scale, aspectRatio: newValue.aspectRatio, } ); @@ -587,8 +583,8 @@ export default function Image( { { img }
      ; } else { + const numericRatio = aspectRatio && evalAspectRatio( aspectRatio ); + const customRatio = numericWidth / numericHeight; const ratio = - ( aspectRatio && evalAspectRatio( aspectRatio ) ) || - ( width && height && width / height ) || - naturalWidth / naturalHeight || - 1; - - const currentWidth = ! width && height ? height * ratio : width; - const currentHeight = ! height && width ? width / ratio : height; + numericRatio || customRatio || naturalWidth / naturalHeight || 1; + const currentWidth = + ! numericWidth && numericHeight + ? numericHeight * ratio + : numericWidth; + const currentHeight = + ! numericHeight && numericWidth + ? numericWidth / ratio + : numericHeight; const minWidth = naturalWidth < naturalHeight ? MIN_SIZE : MIN_SIZE * ratio; @@ -687,10 +687,14 @@ export default function Image( { onResizeStart={ onResizeStart } onResizeStop={ ( event, direction, elt ) => { onResizeStop(); + // Since the aspect ratio is locked when resizing, we can + // use the width of the resized element to calculate the + // height in CSS to prevent stretching when the max-width + // is reached. setAttributes( { - width: elt.offsetWidth, - height: elt.offsetHeight, - aspectRatio: undefined, + width: `${ elt.offsetWidth }px`, + height: 'auto', + aspectRatio: `${ ratio }`, } ); } } resizeRatio={ align === 'center' ? 2 : 1 } diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index 6fa8c6b2342f32..81565af09ababf 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -61,8 +61,6 @@ export default function save( { attributes } ) { width, height, } } - width={ width } - height={ height } title={ title } /> ); diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html index 7ce56e11fa75e9..9a66da6c018989 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html @@ -1,3 +1,3 @@ -
      +
      diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html index 7ce56e11fa75e9..9a66da6c018989 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html @@ -1,3 +1,3 @@ -
      +
      diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html index cfdc52e3cbb6ea..99da2155bce88f 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html @@ -1,3 +1,3 @@ -
      +
      diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html index ad70fe65f35827..807ba3abc9f9ce 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html @@ -1,3 +1,3 @@ -
      +
      diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.html b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.html new file mode 100644 index 00000000000000..7210aee3564b5f --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.html @@ -0,0 +1,3 @@ + +
      + diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.json b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.json new file mode 100644 index 00000000000000..b49c515f8e5ab6 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.json @@ -0,0 +1,15 @@ +[ + { + "name": "core/image", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "sizeSlug": "large", + "width": "164px", + "height": "164px" + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.parsed.json b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.parsed.json new file mode 100644 index 00000000000000..fbd8627807b3da --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.parsed.json @@ -0,0 +1,15 @@ +[ + { + "blockName": "core/image", + "attrs": { + "width": 164, + "height": 164, + "sizeSlug": "large" + }, + "innerBlocks": [], + "innerHTML": "\n
      \"\"
      \n", + "innerContent": [ + "\n
      \"\"
      \n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.serialized.html new file mode 100644 index 00000000000000..5392d261af89d6 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v7-string-width-height-attributes.serialized.html @@ -0,0 +1,3 @@ + +
      + From 11766ffd343977a45d4e42eb5e7be60c3def02e0 Mon Sep 17 00:00:00 2001 From: Siobhan Bamber Date: Thu, 3 Aug 2023 19:21:57 +0100 Subject: [PATCH 060/150] Mobile Release v1.101.0 (#53294) Mobile Release v1.101.0 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 3 ++- packages/react-native-editor/ios/Podfile.lock | 8 ++++---- packages/react-native-editor/package.json | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index ebc7a6b76caffd..34315c42bcfb5b 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.100.2", + "version": "1.101.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 70ebc5f22846c0..ceadb478a63266 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.100.2", + "version": "1.101.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 aa5274fe249004..ff092e1f681ad5 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.101.0 - [*] Remove visual gap in mobile toolbar when a Gallery block is selected [#52966] - [*] Remove Gallery caption button on mobile [#53010] - [**] Upgrade React Native to 0.71.11 [#51303] @@ -17,7 +19,6 @@ For each user feature we should also add a importance categorization label to i - [*] Fix Gallery block selection when adding media [#53127] ## 1.100.2 -======= - [**] Fix iOS Focus loop for RichText components [#53217] ## 1.100.1 diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 1f748c877f23bb..4106127c75b33e 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.71.11) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.100.2): + - Gutenberg (1.101.0): - React-Core (= 0.71.11) - React-CoreModules (= 0.71.11) - React-RCTImage (= 0.71.11) @@ -394,7 +394,7 @@ PODS: - React-RCTImage - RNSVG (13.9.0): - React-Core - - RNTAztecView (1.100.2): + - RNTAztecView (1.101.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - SDWebImage (5.11.1): @@ -577,7 +577,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: f07662560742d82a5b73cee116c70b0b49bcc220 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - Gutenberg: d727d9e158d57edf635d6851c3ed6780a60ecc2b + Gutenberg: d0808b714cad9e66130dcce03f5030ac09f0276d libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: f6187ec763637e6a57f5728dd9a3bdabc6d6b4e0 @@ -620,7 +620,7 @@ SPEC CHECKSUMS: RNReanimated: df2567658c01135f9ff4709d372675bcb9fd1d83 RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789 RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 - RNTAztecView: 769d2a4bc43758cf2977cdc429cdd4b1b79230bc + RNTAztecView: eace043b38d25193a2e7d9dfce1ce890b9348b98 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 00fabf738ec4d1..c2de53ad13045b 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.100.2", + "version": "1.101.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From e70d41914ff3c27187b3e4acfebe9db2a5843a0b Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Thu, 3 Aug 2023 21:10:45 +0200 Subject: [PATCH 061/150] Rich text: copy tag name on internal paste (#48254) --- docs/reference-guides/core-blocks.md | 2 +- .../components/rich-text/use-paste-handler.js | 27 ++----- packages/block-library/src/heading/block.json | 1 - packages/blocks/README.md | 1 + .../src/api/raw-handling/paste-handler.js | 70 ++++++++++++------- .../__snapshots__/rich-text.test.js.snap | 16 ++++- .../specs/editor/various/rich-text.test.js | 11 +++ .../src/component/use-copy-handler.js | 13 ++-- test/integration/blocks-raw-handling.test.js | 6 +- 9 files changed, 89 insertions(+), 58 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 51813220e58240..d6332d1543e8f1 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -311,7 +311,7 @@ Introduce new sections and organize content to help visitors (and search engines - **Name:** core/heading - **Category:** text -- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, className, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight) +- **Supports:** align (full, wide), anchor, className, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight) - **Attributes:** content, level, placeholder, textAlign ## Home Link diff --git a/packages/block-editor/src/components/rich-text/use-paste-handler.js b/packages/block-editor/src/components/rich-text/use-paste-handler.js index 67c932aceddcc1..d86691ced70978 100644 --- a/packages/block-editor/src/components/rich-text/use-paste-handler.js +++ b/packages/block-editor/src/components/rich-text/use-paste-handler.js @@ -129,29 +129,6 @@ export function usePasteHandler( props ) { } const files = [ ...getFilesFromDataTransfer( clipboardData ) ]; - const isInternal = clipboardData.getData( 'rich-text' ) === 'true'; - - // If the data comes from a rich text instance, we can directly use it - // without filtering the data. The filters are only meant for externally - // pasted content and remove inline styles. - if ( isInternal ) { - const pastedMultilineTag = - clipboardData.getData( 'rich-text-multi-line-tag' ) || - undefined; - let pastedValue = create( { - html, - multilineTag: pastedMultilineTag, - multilineWrapperTags: - pastedMultilineTag === 'li' - ? [ 'ul', 'ol' ] - : undefined, - preserveWhiteSpace, - } ); - pastedValue = adjustLines( pastedValue, !! multilineTag ); - addActiveFormats( pastedValue, value.activeFormats ); - onChange( insert( value, pastedValue ) ); - return; - } if ( pastePlainText ) { onChange( insert( value, create( { text: plainText } ) ) ); @@ -238,6 +215,10 @@ export function usePasteHandler( props ) { mode, tagName, preserveWhiteSpace, + // If the data comes from a rich text instance, we can directly + // use it without filtering the data. The filters are only meant + // for externally pasted content and remove inline styles. + disableFilters: !! clipboardData.getData( 'rich-text' ), } ); if ( typeof content === 'string' ) { diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 80f1f0c47b5a8f..9c544e8f95c4f4 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -61,7 +61,6 @@ "textTransform": true } }, - "__unstablePasteTextInline": true, "__experimentalSlashInserter": true }, "editorStyle": "wp-block-heading-editor", diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 91cfec30c6a726..01547ca24ef683 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -458,6 +458,7 @@ _Parameters_ - _options.mode_ `[string]`: Handle content as blocks or inline content. _ 'AUTO': Decide based on the content passed. _ 'INLINE': Always handle as inline content, and return string. \* 'BLOCKS': Always handle as blocks, and return array of blocks. - _options.tagName_ `[Array]`: The tag into which content will be inserted. - _options.preserveWhiteSpace_ `[boolean]`: Whether or not to preserve consequent white space. +- _options.disableFilters_ `[boolean]`: Whether or not to filter non semantic content. _Returns_ diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index c4ad40e0b1f509..f0acfac9fa7614 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -8,7 +8,6 @@ import { getPhrasingContentSchema, removeInvalidHTML } from '@wordpress/dom'; */ import { htmlToBlocks } from './html-to-blocks'; import { hasBlockSupport } from '../registration'; -import { getBlockInnerHTML } from '../serializer'; import parse from '../parser'; import normaliseBlocks from './normalise-blocks'; import specialCommentConverter from './special-comment-converter'; @@ -66,6 +65,40 @@ function filterInlineHTML( HTML, preserveWhiteSpace ) { return HTML; } +/** + * If we're allowed to return inline content, and there is only one inlineable + * block, and the original plain text content does not have any line breaks, + * then treat it as inline paste. + * + * @param {Object} options + * @param {Array} options.blocks + * @param {string} options.plainText + * @param {string} options.mode + */ +function maybeConvertToInline( { blocks, plainText, mode } ) { + if ( + mode === 'AUTO' && + blocks.length === 1 && + hasBlockSupport( blocks[ 0 ].name, '__unstablePasteTextInline', false ) + ) { + const trimRegex = /^[\n]+|[\n]+$/g; + // Don't catch line breaks at the start or end. + const trimmedPlainText = plainText.replace( trimRegex, '' ); + + if ( + trimmedPlainText !== '' && + trimmedPlainText.indexOf( '\n' ) === -1 + ) { + const target = blocks[ 0 ].innerBlocks.length + ? blocks[ 0 ].innerBlocks[ 0 ] + : blocks[ 0 ]; + return target.attributes.content; + } + } + + return blocks; +} + /** * Converts an HTML string to known blocks. Strips everything else. * @@ -79,6 +112,7 @@ function filterInlineHTML( HTML, preserveWhiteSpace ) { * @param {Array} [options.tagName] The tag into which content will be inserted. * @param {boolean} [options.preserveWhiteSpace] Whether or not to preserve consequent white space. * + * @param {boolean} [options.disableFilters] Whether or not to filter non semantic content. * @return {Array|string} A list of blocks or a string, depending on `handlerMode`. */ export function pasteHandler( { @@ -87,6 +121,7 @@ export function pasteHandler( { mode = 'AUTO', tagName, preserveWhiteSpace, + disableFilters, } ) { // First of all, strip any meta tags. HTML = HTML.replace( /]+>/g, '' ); @@ -121,6 +156,14 @@ export function pasteHandler( { HTML = HTML.normalize(); } + if ( disableFilters ) { + return maybeConvertToInline( { + blocks: htmlToBlocks( normaliseBlocks( HTML ), pasteHandler ), + plainText, + mode, + } ); + } + // Parse Markdown (and encoded HTML) if: // * There is a plain text version. // * There is no HTML version, or it has no formatting. @@ -219,28 +262,5 @@ export function pasteHandler( { .flat() .filter( Boolean ); - // If we're allowed to return inline content, and there is only one - // inlineable block, and the original plain text content does not have any - // line breaks, then treat it as inline paste. - if ( - mode === 'AUTO' && - blocks.length === 1 && - hasBlockSupport( blocks[ 0 ].name, '__unstablePasteTextInline', false ) - ) { - const trimRegex = /^[\n]+|[\n]+$/g; - // Don't catch line breaks at the start or end. - const trimmedPlainText = plainText.replace( trimRegex, '' ); - - if ( - trimmedPlainText !== '' && - trimmedPlainText.indexOf( '\n' ) === -1 - ) { - return removeInvalidHTML( - getBlockInnerHTML( blocks[ 0 ] ), - phrasingContentSchema - ).replace( trimRegex, '' ); - } - } - - return blocks; + return maybeConvertToInline( { blocks, plainText, mode } ); } diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap index 5d9c235e1c6a2e..7705ff11cbff9d 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap @@ -24,6 +24,16 @@ exports[`RichText should apply multiple formats when selection is collapsed 1`] " `; +exports[`RichText should copy/paste heading 1`] = ` +" +

      Heading

      + + + +

      Heading

      +" +`; + exports[`RichText should handle Home and End keys 1`] = ` "

      -12+

      @@ -129,7 +139,11 @@ exports[`RichText should paste paragraph contents into list 1`] = `
        -
      • 1
        2
      • +
      • 1
      • + + + +
      • 2
      " `; diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js index 41b2c7253dfa2e..ff651e61d52ea9 100644 --- a/packages/e2e-tests/specs/editor/various/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -556,4 +556,15 @@ describe( 'RichText', () => { // Expect: 1-2 expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + test( 'should copy/paste heading', async () => { + await insertBlock( 'Heading' ); + await page.keyboard.type( 'Heading' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'c' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'v' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/rich-text/src/component/use-copy-handler.js b/packages/rich-text/src/component/use-copy-handler.js index 45a7e1e0cfb8e2..b25e64428a1b4d 100644 --- a/packages/rich-text/src/component/use-copy-handler.js +++ b/packages/rich-text/src/component/use-copy-handler.js @@ -29,18 +29,21 @@ export function useCopyHandler( props ) { const selectedRecord = slice( record.current ); const plainText = getTextContent( selectedRecord ); - const html = toHTMLString( { + const tagName = element.tagName.toLowerCase(); + + let html = toHTMLString( { value: selectedRecord, multilineTag, preserveWhiteSpace, } ); + + if ( tagName && tagName !== 'span' && tagName !== 'div' ) { + html = `<${ tagName }>${ html }`; + } + event.clipboardData.setData( 'text/plain', plainText ); event.clipboardData.setData( 'text/html', html ); event.clipboardData.setData( 'rich-text', 'true' ); - event.clipboardData.setData( - 'rich-text-multi-line-tag', - multilineTag || '' - ); event.preventDefault(); if ( event.type === 'cut' ) { diff --git a/test/integration/blocks-raw-handling.test.js b/test/integration/blocks-raw-handling.test.js index 2a31d0b0ceaa28..733ae308c851ae 100644 --- a/test/integration/blocks-raw-handling.test.js +++ b/test/integration/blocks-raw-handling.test.js @@ -291,9 +291,11 @@ describe( 'Blocks raw handling', () => { HTML: '

      FOO

      ', plainText: 'FOO\n', mode: 'AUTO', - } ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( 'FOO' ); + expect( filtered ).toBe( '

      FOO

      ' ); expect( console ).toHaveLogged(); } ); From f040080a5efe1ceaf5a69829a7a4525811f312d0 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 4 Aug 2023 09:32:13 +1000 Subject: [PATCH 062/150] Global styles revisions: reduce visibility check from 2 to 1 revision (#53281) * Now that https://github.com/WordPress/gutenberg/pull/52965 has merged, we can show the revisions panel when there are more than 0 revisions since there is the default with which we can compare the single revision * Update E2E test to reflect the changes in this PR --- .../src/components/global-styles/ui.js | 2 +- .../index.js | 2 +- .../user-global-styles-revisions.spec.js | 17 ++--------------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 3e4ce1844395a5..2ac94a78609847 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -139,7 +139,7 @@ function GlobalStylesRevisionsMenu() { goTo( '/revisions' ); setEditorCanvasContainerView( 'global-styles-revisions' ); }; - const hasRevisions = revisionsCount >= 1; + const hasRevisions = revisionsCount > 0; return ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js index ce689c095d7eb5..a156bd40c22115 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js @@ -222,7 +222,7 @@ export default function SidebarNavigationScreenGlobalStyles() { }, [ openGlobalStyles, setEditorCanvasContainerView ] ); // If there are no revisions, do not render a footer. - const hasRevisions = revisionsCount >= 2; + const hasRevisions = revisionsCount > 0; const modifiedDateTime = revisions?.[ 0 ]?.modified; const shouldShowGlobalStylesFooter = hasRevisions && ! isLoadingRevisions && modifiedDateTime; diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js index 406e31f9f88f31..73d49741b825b3 100644 --- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js +++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js @@ -26,7 +26,7 @@ test.describe( 'Global styles revisions', () => { await admin.visitSiteEditor(); } ); - test( 'should display revisions UI when there is more than 1 revision', async ( { + test( 'should display revisions UI when there is 1 revision', async ( { page, editor, userGlobalStylesRevisions, @@ -39,19 +39,6 @@ test.describe( 'Global styles revisions', () => { // Change a style and save it. await page.getByRole( 'button', { name: 'Colors styles' } ).click(); - await page - .getByRole( 'button', { name: 'Color Background styles' } ) - .click(); - await page - .getByRole( 'button', { name: 'Color: Black' } ) - .click( { force: true } ); - - await editor.saveSiteEditorEntities(); - - /* - * Change a style and save it again. - * We need more than 2 revisions to show the UI. - */ await page .getByRole( 'button', { name: 'Color Background styles' } ) .click(); @@ -70,7 +57,7 @@ test.describe( 'Global styles revisions', () => { // There should be 2 revisions not including the reset to theme defaults button. await expect( revisionButtons ).toHaveCount( - currentRevisions.length + 2 + currentRevisions.length + 1 ); } ); From 9dfe09471263c75bc7e7df5d9192701c0018f338 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Fri, 4 Aug 2023 09:37:25 +1000 Subject: [PATCH 063/150] [RNMobile] Display custom color value in mobile Cover Block color picker (#51414) * Display custom color value in mobile color picker * Update Cover block bottomLabelText to use proper hex color style * Update text color style * Update CHANGELOG * Update getBottomLabelText from a function to a const * Add test for Cover block custom color picker * Update Cover block color picker test * test: Fix Cover block color picker test (#52943) * test: Mock required styles for Cover block tests JavaScript logic fails due to undefined references from mocked Sass files. * test: Fix Cover block HEX test assertions Synchronous queries of user-facing text is generally preferred over test IDs or asynchronous queries. Additionally, the `react-native-hsv-color-picker` must be mocked to trigger the required interaction events. Lastly, selecting the white color results in a non-custom color, which does not appear to display the HEX value in the picker footer label. --------- Co-authored-by: David Calhoun --- .../block-library/src/cover/edit.native.js | 16 ++++++++- .../block-library/src/cover/style.native.scss | 11 ++++++ .../src/cover/test/edit.native.js | 36 ++++++++++++++++++- .../block-library/src/missing/edit.native.js | 1 + packages/react-native-editor/CHANGELOG.md | 13 +++---- test/native/__mocks__/styleMock.js | 2 ++ test/native/setup.js | 8 +++-- 7 files changed, 76 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 8a6c07ab4878fa..a34476d67180a9 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -6,6 +6,7 @@ import { TouchableWithoutFeedback, InteractionManager, AccessibilityInfo, + Text, Platform, } from 'react-native'; import Video from 'react-native-video'; @@ -364,6 +365,19 @@ const Cover = ( { } ); }, [] ); + const selectedColorText = getStylesFromColorScheme( + styles.selectedColorText, + styles.selectedColorTextDark + ); + + const bottomLabelText = customOverlayColor ? ( + + { customOverlayColor.toUpperCase() } + + ) : ( + __( 'Select a color' ) + ); + const colorPickerControls = ( @@ -393,7 +407,7 @@ const Cover = ( { isBottomSheetContentScrolling={ isBottomSheetContentScrolling } - bottomLabelText={ __( 'Select a color' ) } + bottomLabelText={ bottomLabelText } /> ) } diff --git a/packages/block-library/src/cover/style.native.scss b/packages/block-library/src/cover/style.native.scss index c121f9cb35bdeb..15ca9bcc5105ee 100644 --- a/packages/block-library/src/cover/style.native.scss +++ b/packages/block-library/src/cover/style.native.scss @@ -207,3 +207,14 @@ .mediaPlaceholderEmptyStateContainer { height: 300; } + +.selectedColorText { + font-family: $default-monospace-font; + color: $light-primary; + font-size: 16px; + font-weight: 400; +} + +.selectedColorTextDark { + color: $dark-primary; +} diff --git a/packages/block-library/src/cover/test/edit.native.js b/packages/block-library/src/cover/test/edit.native.js index 0e61207bc0da3d..d5dffb3161a52a 100644 --- a/packages/block-library/src/cover/test/edit.native.js +++ b/packages/block-library/src/cover/test/edit.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Image } from 'react-native'; +import { Image, Pressable } from 'react-native'; import { getEditorHtml, initializeEditor, @@ -12,6 +12,7 @@ import { getBlock, openBlockSettings, } from 'test/helpers'; +import HsvColorPicker from 'react-native-hsv-color-picker'; /** * WordPress dependencies @@ -538,6 +539,39 @@ describe( 'color settings', () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); + + it( 'displays the hex color value in the custom color picker', async () => { + HsvColorPicker.mockImplementation( ( props ) => { + return ; + } ); + const screen = await initializeEditor( { + initialHtml: COVER_BLOCK_PLACEHOLDER_HTML, + } ); + + // Select a color from the placeholder palette. + const colorButton = screen.getByA11yHint( + 'Navigates to custom color picker' + ); + fireEvent.press( colorButton ); + + // Wait for Block Settings to be visible. + const blockSettingsModal = screen.getByTestId( 'block-settings-modal' ); + await waitForModalVisible( blockSettingsModal ); + + // Assert label text before tapping color picker + expect( screen.getByText( 'Select a color' ) ).toBeVisible(); + + // Tap color picker + const colorPicker = screen.getByTestId( 'hsv-color-picker' ); + fireEvent( colorPicker, 'onHuePickerPress', { + hue: 120, + saturation: 12, + value: 50, + } ); + + // Assert label hex value after tapping color picker + expect( screen.getByText( '#00FF00' ) ).toBeVisible(); + } ); } ); describe( 'minimum height settings', () => { diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index e8576914065723..cf590dc0181c4f 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -255,6 +255,7 @@ export class UnsupportedBlockEdit extends Component { styles.unsupportedBlockSubtitle, styles.unsupportedBlockSubtitleDark ); + const subtitle = ( { __( 'Unsupported' ) } ); diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index ff092e1f681ad5..0b483873048dfe 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,7 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased +- [*] Display custom color value in mobile Cover Block color picker [#51414] ## 1.101.0 - [*] Remove visual gap in mobile toolbar when a Gallery block is selected [#52966] @@ -28,8 +29,8 @@ For each user feature we should also add a importance categorization label to i - [**] Add media inserter buttons to editor toolbar [#51827] - [**] Update native BlockOutline component styles to remove blue border from blocks [#51222] - [**] Move the undo/redo buttons to the navigation bar [#51766] -- [**] Update Editor block inserter button styles and default text input placeholder/selection styles [#52269] -- [**] Update Editor toolbar icons and colors [#52336] +- [**] Update Editor block inserter button styles and default text input placeholder/selection styles [#52269] +- [**] Update Editor toolbar icons and colors [#52336] - [*] Update Block Settings button border [#52715] ## 1.99.1 @@ -77,7 +78,7 @@ For each user feature we should also add a importance categorization label to i - [**] Fix regression with the Color hook and ColorPanel. [#49917] ## 1.93.0 -- [***] [iOS] Fixed iOS scroll jumping issue by refactoring KeyboardAwareFlatList improving writing flow and caret focus handling. [#48791] +- [***] [iOS] Fixed iOS scroll jumping issue by refactoring KeyboardAwareFlatList improving writing flow and caret focus handling. [#48791] ## 1.92.1 - [*] Avoid empty Gallery block error [#49557] @@ -572,8 +573,8 @@ For each user feature we should also add a importance categorization label to i ## 1.42.0 - [***] Adding support for selecting different unit of value in Cover and Columns blocks [#26161] -- [**] Button block - Add link picker to the block settings [#26206] -- [**] Support to render background/text colors in Group, Paragraph and Quote blocks [#25994] +- [**] Button block - Add link picker to the block settings [#26206] +- [**] Support to render background/text colors in Group, Paragraph and Quote blocks [#25994] - [*] Fix theme colors syncing with the editor [#26821] - [**] Fix issue where a blocks would disappear when deleting all of the text inside without requiring the extra backspace to remove the block. [#27583] @@ -868,4 +869,4 @@ For each user feature we should also add a importance categorization label to i ## 1.6.0 - Fixed issue with link settings where “Open in New Tab” was always OFF on open. -- Added UI to display a warning when a block has invalid content. +- Added UI to display a warning when a block has invalid content. \ No newline at end of file diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index e06c41cf1f95ef..e0c690b6282349 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -202,4 +202,6 @@ module.exports = { embed__icon: { fill: 'black', }, + picker: {}, + pickerPointer: {}, }; diff --git a/test/native/setup.js b/test/native/setup.js index 0c8e21de3430d0..60b42dc25d2fe1 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -188,9 +188,11 @@ jest.mock( 'react-native-linear-gradient', () => () => 'LinearGradient', { virtual: true, } ); -jest.mock( 'react-native-hsv-color-picker', () => () => 'HsvColorPicker', { - virtual: true, -} ); +jest.mock( + 'react-native-hsv-color-picker', + () => jest.fn( () => 'HsvColorPicker' ), + { virtual: true } +); jest.mock( '@react-native-community/blur', () => () => 'BlurView', { virtual: true, From 339cf1a1c6faa1ad09cebc2b80ea7975378de132 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 4 Aug 2023 06:34:15 +0200 Subject: [PATCH 064/150] File block: Add spacing support (#45107) * File block: Add spacing support --- docs/reference-guides/core-blocks.md | 2 +- packages/block-library/src/file/block.json | 4 ++++ packages/block-library/src/file/style.scss | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index d6332d1543e8f1..8667c9b9cfcaf1 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -266,7 +266,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb - **Name:** core/file - **Category:** media -- **Supports:** align, anchor, color (background, gradients, link, ~~text~~) +- **Supports:** align, anchor, color (background, gradients, link, ~~text~~), spacing (margin, padding) - **Attributes:** displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget ## Footnotes diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index 12edc20630d1ed..576fe34f5cf8f7 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -57,6 +57,10 @@ "supports": { "anchor": true, "align": true, + "spacing": { + "margin": true, + "padding": true + }, "color": { "gradients": true, "link": true, diff --git a/packages/block-library/src/file/style.scss b/packages/block-library/src/file/style.scss index e34935833e13cd..5c9a2f7be2adc6 100644 --- a/packages/block-library/src/file/style.scss +++ b/packages/block-library/src/file/style.scss @@ -1,4 +1,7 @@ .wp-block-file { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; + &:not(.wp-element-button) { font-size: 0.8em; } From 2ee09de65f23d0de7cec088fdfe8b0ab9d933da0 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Fri, 4 Aug 2023 09:37:43 +0400 Subject: [PATCH 065/150] Migrate 'Block variations' e2e tests to Playwright (#53266) * Migrate 'Block variations' e2e tests to Playwright * Delete old test file * Ensure document settings is open --- .../editor/plugins/block-variations.test.js | 191 --------------- .../editor/plugins/block-variations.spec.js | 220 ++++++++++++++++++ 2 files changed, 220 insertions(+), 191 deletions(-) delete mode 100644 packages/e2e-tests/specs/editor/plugins/block-variations.test.js create mode 100644 test/e2e/specs/editor/plugins/block-variations.spec.js diff --git a/packages/e2e-tests/specs/editor/plugins/block-variations.test.js b/packages/e2e-tests/specs/editor/plugins/block-variations.test.js deleted file mode 100644 index 4a24cd3f478d26..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/block-variations.test.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - insertBlock, - searchForBlock, - pressKeyWithModifier, - openDocumentSettingsSidebar, - togglePreferencesOption, - toggleMoreMenu, - canvas, -} from '@wordpress/e2e-test-utils'; - -describe( 'Block variations', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-block-variations' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-block-variations' ); - } ); - - const expectInserterItem = async ( blockTitle ) => { - const inserterItem = await page.$x( - `//button[contains(@class, 'block-editor-block-types-list__item')]//span[text()="${ blockTitle }"]` - ); - expect( inserterItem ).toBeDefined(); - expect( inserterItem ).toHaveLength( 1 ); - }; - - test( 'Search for the overridden default Quote block', async () => { - await searchForBlock( 'Quote' ); - - expect( await page.$( '.editor-block-list-item-quote' ) ).toBeNull(); - expectInserterItem( 'Large Quote' ); - } ); - - test( 'Insert the overridden default Quote block variation', async () => { - await insertBlock( 'Large Quote' ); - - expect( - await canvas().$( - '.wp-block[data-type="core/quote"] blockquote.is-style-large' - ) - ).toBeDefined(); - } ); - - test( 'Insert the Large Quote block variation with slash command', async () => { - await insertBlock( 'Paragraph' ); - - await page.keyboard.type( '/large' ); - await page.keyboard.press( 'Enter' ); - - expect( - await canvas().$( - '.wp-block[data-type="core/quote"] blockquote.is-style-large' - ) - ).toBeDefined(); - } ); - - test( 'Search for the Paragraph block with 2 additional variations', async () => { - await searchForBlock( 'Paragraph' ); - - expectInserterItem( 'Paragraph' ); - expectInserterItem( 'Success Message' ); - expectInserterItem( 'Warning Message' ); - } ); - - test( 'Insert the Success Message block variation', async () => { - await insertBlock( 'Success Message' ); - - const successMessageBlock = await canvas().$( - '.wp-block[data-type="core/paragraph"]' - ); - expect( successMessageBlock ).toBeDefined(); - expect( - await successMessageBlock.evaluate( ( node ) => node.innerText ) - ).toBe( 'This is a success message!' ); - } ); - test( 'Pick the additional variation in the inserted Columns block', async () => { - await insertBlock( 'Columns' ); - - const fourColumnsVariation = await canvas().waitForSelector( - '.wp-block[data-type="core/columns"] .block-editor-block-variation-picker__variation[aria-label="Four columns"]' - ); - await fourColumnsVariation.click(); - expect( - await canvas().$$( - '.wp-block[data-type="core/columns"] .wp-block[data-type="core/column"]' - ) - ).toHaveLength( 4 ); - } ); - // @see @wordpres/block-editor/src/components/use-block-display-information (`useBlockDisplayInformation` hook). - describe( 'testing block display information with matching variations', () => { - beforeEach( async () => { - await togglePreferencesOption( - 'General', - 'Display block breadcrumbs', - true - ); - await toggleMoreMenu( 'close' ); - } ); - - afterEach( async () => { - await togglePreferencesOption( - 'General', - 'Display block breadcrumbs', - false - ); - await toggleMoreMenu( 'close' ); - } ); - - const getActiveBreadcrumb = async () => - page.evaluate( - () => - document.querySelector( - '.block-editor-block-breadcrumb__current' - ).textContent - ); - const getFirstNavigationItem = async () => { - await pressKeyWithModifier( 'access', 'o' ); - // This also returns the visually hidden text `(selected block)`. - // For example `Paragraph(selected block)`. In order to hide this - // implementation detail and search for childNodes, we choose to - // test with `String.prototype.startsWith()`. - return page.evaluate( - () => - document.querySelector( - '.block-editor-list-view-block-select-button' - ).textContent - ); - }; - const getBlockCardDescription = async () => { - await openDocumentSettingsSidebar(); - return page.evaluate( - () => - document.querySelector( - '.block-editor-block-card__description' - ).textContent - ); - }; - - it( 'should show block information when no matching variation is found', async () => { - await insertBlock( 'Large Quote' ); - // Select the quote block. - await page.keyboard.press( 'ArrowDown' ); - const breadcrumb = await getActiveBreadcrumb(); - expect( breadcrumb ).toEqual( 'Quote' ); - const navigationItem = await getFirstNavigationItem(); - expect( navigationItem.startsWith( 'Quote' ) ).toBeTruthy(); - const description = await getBlockCardDescription(); - expect( description ).toEqual( - 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar' - ); - } ); - it( 'should display variations info if all declared', async () => { - await insertBlock( 'Success Message' ); - const breadcrumb = await getActiveBreadcrumb(); - expect( breadcrumb ).toEqual( 'Success Message' ); - const navigationItem = await getFirstNavigationItem(); - expect( - navigationItem.startsWith( 'Success Message' ) - ).toBeTruthy(); - const description = await getBlockCardDescription(); - expect( description ).toEqual( - 'This block displays a success message. This description overrides the default one provided for the Paragraph block.' - ); - } ); - it( 'should display mixed block and variation match information', async () => { - // Warning Message variation is missing the `description`. - await insertBlock( 'Warning Message' ); - const breadcrumb = await getActiveBreadcrumb(); - expect( breadcrumb ).toEqual( 'Warning Message' ); - const navigationItem = await getFirstNavigationItem(); - expect( - navigationItem.startsWith( 'Warning Message' ) - ).toBeTruthy(); - const description = await getBlockCardDescription(); - expect( description ).toEqual( - 'Start with the basic building block of all narrative.' - ); - } ); - } ); -} ); diff --git a/test/e2e/specs/editor/plugins/block-variations.spec.js b/test/e2e/specs/editor/plugins/block-variations.spec.js new file mode 100644 index 00000000000000..302bac732023c5 --- /dev/null +++ b/test/e2e/specs/editor/plugins/block-variations.spec.js @@ -0,0 +1,220 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Block variations', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( 'gutenberg-test-block-variations' ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-block-variations' + ); + } ); + + test( 'Search for the overridden default Quote block', async ( { + page, + } ) => { + await page + .getByRole( 'button', { name: 'Toggle block inserter' } ) + .click(); + + await page + .getByRole( 'region', { name: 'Block Library' } ) + .getByRole( 'searchbox', { + name: 'Search for blocks and patterns', + } ) + .fill( 'Quote' ); + + await expect( + page + .getByRole( 'listbox', { name: 'Blocks' } ) + .getByRole( 'option', { name: 'Quote', exact: true } ) + ).toBeHidden(); + await expect( + page + .getByRole( 'listbox', { name: 'Blocks' } ) + .getByRole( 'option', { name: 'Large Quote' } ) + ).toBeVisible(); + } ); + + test( 'Insert the overridden default Quote block variation', async ( { + editor, + page, + } ) => { + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/Large Quote' ); + await page.keyboard.press( 'Enter' ); + + await expect( + editor.canvas.getByRole( 'document', { name: 'Block: Quote' } ) + ).toHaveClass( /is-style-large/ ); + } ); + + test( 'Search for the Paragraph block with 2 additional variations', async ( { + page, + } ) => { + await page + .getByRole( 'button', { name: 'Toggle block inserter' } ) + .click(); + + await page + .getByRole( 'region', { name: 'Block Library' } ) + .getByRole( 'searchbox', { + name: 'Search for blocks and patterns', + } ) + .fill( 'Paragraph' ); + + await expect( + page + .getByRole( 'listbox', { name: 'Blocks' } ) + .getByRole( 'option' ) + ).toHaveText( [ 'Paragraph', 'Success Message', 'Warning Message' ] ); + } ); + + test( 'Insert the Success Message block variation', async ( { + editor, + page, + } ) => { + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/Heading' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '/Success Message' ); + await page.keyboard.press( 'Enter' ); + + await expect( + editor.canvas.getByRole( 'document', { name: 'Paragraph block' } ) + ).toHaveText( 'This is a success message!' ); + } ); + + test( 'Pick the additional variation in the inserted Columns block', async ( { + editor, + page, + } ) => { + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/Columns' ); + await page.keyboard.press( 'Enter' ); + + await editor.canvas + .getByRole( 'list', { name: 'Block variations' } ) + .getByRole( 'button', { name: 'Four columns' } ) + .click(); + + await expect( + editor.canvas + .getByRole( 'document', { name: 'Block: Columns' } ) + .getByRole( 'document' ) + ).toHaveCount( 4 ); + } ); + + // Tests the `useBlockDisplayInformation` hook. + test( 'should show block information when no matching variation is found', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.openDocumentSettingsSidebar(); + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/Large Quote' ); + await page.keyboard.press( 'Enter' ); + + // Select the quote block. + await page.keyboard.press( 'ArrowDown' ); + + await expect( + page + .getByRole( 'list', { name: 'Block breadcrumb' } ) + .getByRole( 'listitem' ) + .filter( { hasText: 'Quote' } ) + ).toHaveAttribute( 'aria-current', 'true' ); + + await pageUtils.pressKeys( 'access+o' ); + + await expect( + page + .getByRole( 'treegrid', { name: 'Block navigation structure' } ) + .getByRole( 'link' ) + ).toHaveText( 'Quote' ); + + await expect( + page.locator( '.block-editor-block-card__description' ) + ).toHaveText( + 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar' + ); + } ); + + test( 'should display variations info if all declared', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.openDocumentSettingsSidebar(); + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/Heading' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '/Success Message' ); + await page.keyboard.press( 'Enter' ); + + await expect( + page + .getByRole( 'list', { name: 'Block breadcrumb' } ) + .getByRole( 'listitem' ) + .filter( { hasText: 'Success Message' } ) + ).toHaveAttribute( 'aria-current', 'true' ); + + await pageUtils.pressKeys( 'access+o' ); + + await expect( + page + .getByRole( 'treegrid', { name: 'Block navigation structure' } ) + .getByRole( 'link' ) + ).toHaveText( 'Success Message' ); + + await expect( + page.locator( '.block-editor-block-card__description' ) + ).toHaveText( + 'This block displays a success message. This description overrides the default one provided for the Paragraph block.' + ); + } ); + + test( 'should display mixed block and variation match information', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.openDocumentSettingsSidebar(); + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/Heading' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '/Warning Message' ); + await page.keyboard.press( 'Enter' ); + + await expect( + page + .getByRole( 'list', { name: 'Block breadcrumb' } ) + .getByRole( 'listitem' ) + .filter( { hasText: 'Warning Message' } ) + ).toHaveAttribute( 'aria-current', 'true' ); + + await pageUtils.pressKeys( 'access+o' ); + + await expect( + page + .getByRole( 'treegrid', { + name: 'Block navigation structure', + } ) + .getByRole( 'link' ) + ).toHaveText( 'Warning Message' ); + + // Warning Message variation is missing the `description`. + await expect( + page.locator( '.block-editor-block-card__description' ) + ).toHaveText( 'Start with the basic building block of all narrative.' ); + } ); +} ); From 02566bc00de19e3c6c9e8aa475d14bed41395a44 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 4 Aug 2023 16:10:12 +1000 Subject: [PATCH 066/150] Fluid typography: add min and max viewport width configurable options (#53081) * Initial commit: - add min and max viewport width to the theme.json schema - supporting min and max viewport widths in PHP typography block supports * Removed camel * Fixed typo in schema Max and min viewport values should be taken from $fluid_settings ;-P * Updating tests * Cleaning up defaults lists so the values are defaults only. Should make it easier to read. --- lib/block-supports/typography.php | 20 ++++++--- .../global-styles/test/typography-utils.js | 45 +++++++++++++++++++ .../global-styles/typography-utils.js | 1 + phpunit/block-supports/typography-test.php | 2 +- .../theme.json | 4 +- schemas/json/theme.json | 10 ++++- 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 047231a6eed105..bdcb2bbe988968 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -459,13 +459,21 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty $fluid_settings = isset( $typography_settings['fluid'] ) && is_array( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); // Defaults. - $default_maximum_viewport_width = isset( $layout_settings['wideSize'] ) ? $layout_settings['wideSize'] : '1600px'; + $default_maximum_viewport_width = '1600px'; $default_minimum_viewport_width = '320px'; $default_minimum_font_size_factor_max = 0.75; $default_minimum_font_size_factor_min = 0.25; $default_scale_factor = 1; - $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( gutenberg_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); - $default_minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : '14px'; + $default_minimum_font_size_limit = '14px'; + + // Defaults overrides. + $minimum_viewport_width = isset( $fluid_settings['minViewportWidth'] ) ? $fluid_settings['minViewportWidth'] : $default_minimum_viewport_width; + $maximum_viewport_width = isset( $layout_settings['wideSize'] ) ? $layout_settings['wideSize'] : $default_maximum_viewport_width; + if ( isset( $fluid_settings['maxViewportWidth'] ) ) { + $maximum_viewport_width = $fluid_settings['maxViewportWidth']; + } + $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( gutenberg_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); + $minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : $default_minimum_font_size_limit; // Font sizes. $fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null; @@ -489,7 +497,7 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty // Parses the minimum font size limit, so we can perform checks using it. $minimum_font_size_limit = gutenberg_get_typography_value_and_unit( - $default_minimum_font_size_limit, + $minimum_font_size_limit, array( 'coerce_to' => $preferred_size['unit'], ) @@ -538,8 +546,8 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty $fluid_font_size_value = gutenberg_get_computed_fluid_typography_value( array( - 'minimum_viewport_width' => $default_minimum_viewport_width, - 'maximum_viewport_width' => $default_maximum_viewport_width, + 'minimum_viewport_width' => $minimum_viewport_width, + 'maximum_viewport_width' => $maximum_viewport_width, 'minimum_font_size' => $minimum_font_size_raw, 'maximum_font_size' => $maximum_font_size_raw, 'scale_factor' => $default_scale_factor, diff --git a/packages/block-editor/src/components/global-styles/test/typography-utils.js b/packages/block-editor/src/components/global-styles/test/typography-utils.js index 4bdcd3d1d22d89..a03b5f2552dadd 100644 --- a/packages/block-editor/src/components/global-styles/test/typography-utils.js +++ b/packages/block-editor/src/components/global-styles/test/typography-utils.js @@ -95,6 +95,36 @@ describe( 'typography utils', () => { 'clamp(1.119rem, 1.119rem + ((1vw - 0.2rem) * 0.789), 1.75rem)', }, + { + message: + 'should override default max viewport width fluid typography settings', + preset: { + size: '1.75rem', + }, + typographySettings: { + fluid: { + maxViewportWidth: '1200px', + }, + }, + expected: + 'clamp(1.119rem, 1.119rem + ((1vw - 0.2rem) * 1.147), 1.75rem)', + }, + + { + message: + 'should override default min viewport width fluid typography settings', + preset: { + size: '1.75rem', + }, + typographySettings: { + fluid: { + minViewportWidth: '800px', + }, + }, + expected: + 'clamp(1.119rem, 1.119rem + ((1vw - 0.5rem) * 1.262), 1.75rem)', + }, + { message: 'should return clamp value with em min and max units', preset: { @@ -478,6 +508,21 @@ describe( 'typography utils', () => { expected: 'clamp(100px, 6.25rem + ((1vw - 3.2px) * 7.813), 200px)', }, + + { + message: 'should apply all custom fluid typography settings', + preset: { + size: '17px', + }, + typographySettings: { + fluid: { + minFontSize: '16px', + maxViewportWidth: '1200px', + minViewportWidth: '640px', + }, + }, + expected: 'clamp(16px, 1rem + ((1vw - 6.4px) * 0.179), 17px)', + }, ].forEach( ( { message, preset, typographySettings, expected } ) => { it( `${ message }`, () => { expect( diff --git a/packages/block-editor/src/components/global-styles/typography-utils.js b/packages/block-editor/src/components/global-styles/typography-utils.js index 2e7377e4b663c4..c88019adc38dd8 100644 --- a/packages/block-editor/src/components/global-styles/typography-utils.js +++ b/packages/block-editor/src/components/global-styles/typography-utils.js @@ -68,6 +68,7 @@ export function getTypographyFontSizeValue( preset, typographyOptions ) { fontSize: defaultSize, minimumFontSizeLimit: fluidTypographySettings?.minFontSize, maximumViewportWidth: fluidTypographySettings?.maxViewportWidth, + minimumViewportWidth: fluidTypographySettings?.minViewportWidth, } ); if ( !! fluidFontSizeValue ) { diff --git a/phpunit/block-supports/typography-test.php b/phpunit/block-supports/typography-test.php index e3e136eabc6921..2de0da0af799e6 100644 --- a/phpunit/block-supports/typography-test.php +++ b/phpunit/block-supports/typography-test.php @@ -679,7 +679,7 @@ public function data_generate_block_supports_font_size_fixtures() { 'returns clamp value using custom fluid config' => array( 'font_size_value' => '17px', 'theme_slug' => 'block-theme-child-with-fluid-typography-config', - 'expected_output' => 'font-size:clamp(16px, 1rem + ((1vw - 3.2px) * 0.147), 17px);', + 'expected_output' => 'font-size:clamp(16px, 1rem + ((1vw - 6.4px) * 0.179), 17px);', ), 'returns value when font size <= custom min font size bound' => array( 'font_size_value' => '15px', 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 73864f2920039d..65ed480f20e166 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 @@ -7,7 +7,9 @@ }, "typography": { "fluid": { - "minFontSize": "16px" + "minFontSize": "16px", + "maxViewportWidth": "1200px", + "minViewportWidth": "640px" } } } diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 513dfbd2236fc0..06e588d26f815a 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -449,7 +449,15 @@ "type": "object", "properties": { "minFontSize": { - "description": "Allow users to set a global minimum font size boundary in px, rem or em. Custom font sizes below this value will not be clamped, and all calculated minimum font sizes will be, a at minimum, this value.", + "description": "Allow users to set a global minimum font size boundary in px, rem or em. Custom font sizes below this value will not be clamped, and all calculated minimum font sizes will be, at a minimum, this value.", + "type": "string" + }, + "maxViewportWidth": { + "description": "Allow users to set custom a max viewport width in px, rem or em, used to set the maximum size boundary of a fluid font size.", + "type": "string" + }, + "minViewportWidth": { + "description": "Allow users to set a custom min viewport width in px, rem or em, used to set the minimum size boundary of a fluid font size.", "type": "string" } }, From 685577bedb841856c33e7961a857e4ee17602e85 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 4 Aug 2023 16:20:37 +1000 Subject: [PATCH 067/150] Add layout and block spacing to details block (#53282) * Add layout and block spacing to details block * Remove custom gap values. --- docs/reference-guides/core-blocks.md | 2 +- packages/block-library/src/details/block.json | 4 ++++ packages/block-library/src/details/style.scss | 11 ----------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 8667c9b9cfcaf1..df34a04bcf5d73 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -248,7 +248,7 @@ Hide and show additional content. ([Source](https://github.com/WordPress/gutenbe - **Name:** core/details - **Category:** text -- **Supports:** align (full, wide), color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), color (background, gradients, link, text), layout (~~allowEditing~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** showContent, summary ## Embed diff --git a/packages/block-library/src/details/block.json b/packages/block-library/src/details/block.json index 222be7357c3fb3..421a4d957e3e63 100644 --- a/packages/block-library/src/details/block.json +++ b/packages/block-library/src/details/block.json @@ -35,6 +35,7 @@ "spacing": { "margin": true, "padding": true, + "blockGap": true, "__experimentalDefaultControls": { "margin": false, "padding": false @@ -52,6 +53,9 @@ "__experimentalDefaultControls": { "fontSize": true } + }, + "layout": { + "allowEditing": false } }, "editorStyle": "wp-block-details-editor", diff --git a/packages/block-library/src/details/style.scss b/packages/block-library/src/details/style.scss index 415a3210a50aeb..904e056d049de7 100644 --- a/packages/block-library/src/details/style.scss +++ b/packages/block-library/src/details/style.scss @@ -6,14 +6,3 @@ .wp-block-details summary { cursor: pointer; } - -// Use block gap for block; falls back to browser default if not supported. -.wp-block-details > *:not(summary) { - margin-block-start: var(--wp--style--block-gap); - margin-block-end: 0; -} - -// Remove excess margin from the last block. -.wp-block-details > *:last-child { - margin-bottom: 0; -} From 87f2de3f18e560a1d6101238c948ef46f4866392 Mon Sep 17 00:00:00 2001 From: Noah Allen Date: Fri, 4 Aug 2023 02:04:43 -0700 Subject: [PATCH 068/150] Add missing peer dependencies (#53275) --- packages/annotations/package.json | 3 +++ packages/commands/package.json | 3 ++- packages/core-commands/package.json | 3 ++- packages/core-data/package.json | 3 ++- packages/data-controls/package.json | 3 +++ packages/notices/package.json | 3 +++ packages/plugins/package.json | 3 ++- 7 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index cb7b5f5d0a3b18..b5773e52bf9174 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -33,6 +33,9 @@ "rememo": "^4.0.2", "uuid": "^8.3.0" }, + "peerDependencies": { + "react": "^18.0.0" + }, "publishConfig": { "access": "public" } diff --git a/packages/commands/package.json b/packages/commands/package.json index 5a94853826f624..d08e4d3ff7e2f3 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -38,7 +38,8 @@ "rememo": "^4.0.2" }, "peerDependencies": { - "react": "^18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/core-commands/package.json b/packages/core-commands/package.json index c6cc52454b4b41..dd48586f62872b 100644 --- a/packages/core-commands/package.json +++ b/packages/core-commands/package.json @@ -39,7 +39,8 @@ "@wordpress/url": "file:../url" }, "peerDependencies": { - "react": "^18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/core-data/package.json b/packages/core-data/package.json index f44297ffb713c8..5212ccbf1021a0 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -51,7 +51,8 @@ "uuid": "^8.3.0" }, "peerDependencies": { - "react": "^18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 084260bdcf9567..1d642369a504b0 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -32,6 +32,9 @@ "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated" }, + "peerDependencies": { + "react": "^18.0.0" + }, "publishConfig": { "access": "public" } diff --git a/packages/notices/package.json b/packages/notices/package.json index c1c745cd5b2268..9f76cbd9dbfd8c 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -30,6 +30,9 @@ "@wordpress/a11y": "file:../a11y", "@wordpress/data": "file:../data" }, + "peerDependencies": { + "react": "^18.0.0" + }, "publishConfig": { "access": "public" } diff --git a/packages/plugins/package.json b/packages/plugins/package.json index a3f22666212869..13e5987fed2f31 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -36,7 +36,8 @@ "memize": "^2.0.1" }, "peerDependencies": { - "react": "^18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" From 162eb4b561b7f5ccc6da6f8c5e32ebd722f779f5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:20:06 +0300 Subject: [PATCH 069/150] Components: Expose `Theme` via private APIs (#53262) * Components: Expose Theme as a private API * Remove invalid experimental example from README * Fix inline example to use private API * Add CHANGELOG entry * Remove private API access from Theme inline example --- packages/components/CHANGELOG.md | 1 + packages/components/src/private-apis.ts | 2 ++ packages/components/src/theme/README.md | 17 ----------------- packages/components/src/theme/index.tsx | 2 -- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c39a492f840a27..1fbb88ed83f941 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -11,6 +11,7 @@ - `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)). - `MenuItemsChoice`, `MenuItem`: Support a `disabled` prop on a menu item ([#52737](https://github.com/WordPress/gutenberg/pull/52737)). - `TabPanel`: Introduce a new version of `TabPanel` with updated internals and improved adherence to ARIA guidance on `tabpanel` focus behavior while maintaining the same functionality and API surface.([#52133](https://github.com/WordPress/gutenberg/pull/52133)). +- `Theme`: Expose via private APIs ([#53262](https://github.com/WordPress/gutenberg/pull/53262)). ### Bug Fix diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index b144f348143c60..6e17abde0c627e 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -23,6 +23,7 @@ import { DropdownSubMenuTrigger as DropdownSubMenuTriggerV2, } from './dropdown-menu-v2'; import { ComponentsContext } from './ui/context/context-system-provider'; +import Theme from './theme'; export const { lock, unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules( @@ -47,4 +48,5 @@ lock( privateApis, { DropdownSubMenuV2, DropdownSubMenuTriggerV2, ProgressBar, + Theme, } ); diff --git a/packages/components/src/theme/README.md b/packages/components/src/theme/README.md index 2cc8487dd687e1..d1bfe237c9608f 100644 --- a/packages/components/src/theme/README.md +++ b/packages/components/src/theme/README.md @@ -8,23 +8,6 @@ This feature is still experimental. “Experimental” means this is an early im Multiple `Theme` components can be nested in order to override specific theme variables. -## Usage - -```jsx -import { __experimentalTheme as Theme } from '@wordpress/components'; - -const Example = () => { - return ( - - - - - - - ); -}; -``` - ## Props ### `accent`: `string` diff --git a/packages/components/src/theme/index.tsx b/packages/components/src/theme/index.tsx index 591da45e7c14d9..984ec32a07ce3d 100644 --- a/packages/components/src/theme/index.tsx +++ b/packages/components/src/theme/index.tsx @@ -20,8 +20,6 @@ import { useCx } from '../utils'; * * @example * ```jsx - * import { __experimentalTheme as Theme } from '@wordpress/components'; - * * const Example = () => { * return ( * From 5e54a9dc3f8e9cc798f7a39417fd36f0ffcabb63 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:44:13 +0200 Subject: [PATCH 070/150] Interactivity API: Add support for underscores and leading dashes in the suffix part of the directive (#53337) * Allow BEM classes for wp-class directive * Update changelog, put back a missed $ sign --- .../interactive-blocks/directive-class/render.php | 11 +++++++++++ packages/interactivity/CHANGELOG.md | 4 ++++ packages/interactivity/src/vdom.js | 2 +- test/e2e/specs/interactivity/directives-class.spec.ts | 10 ++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-class/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-class/render.php index 19da052dc1148c..b229418de2f67d 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-class/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-class/render.php @@ -72,4 +72,15 @@ class="foo" Toggle context falseValue
      + +
      + +
      +
    diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index 5a79ee8f5af75c..9188f342ae2f71 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fix + +- Add support for underscores and leading dashes in the suffix part of the directive. ([#53337](https://github.com/WordPress/gutenberg/pull/53337)) + ### Breaking Change - Remove the `wp-show` directive until we figure out its final implementation. ([#53240](https://github.com/WordPress/gutenberg/pull/53240)) diff --git a/packages/interactivity/src/vdom.js b/packages/interactivity/src/vdom.js index fe09f492dbd566..1cf4a91ec1ead5 100644 --- a/packages/interactivity/src/vdom.js +++ b/packages/interactivity/src/vdom.js @@ -21,7 +21,7 @@ const directiveParser = new RegExp( // (Optional) Match '--' followed by any alphanumeric charachters. It // excludes underscore intentionally to prevent confusion, but it can // contain multiple hyphens. E.g., "--custom-prefix--with-more-info". - '(?:--([a-z0-9][a-z0-9-]+))?$', + '(?:--([a-z0-9_-]+))?$', 'i' // Case insensitive. ); diff --git a/test/e2e/specs/interactivity/directives-class.spec.ts b/test/e2e/specs/interactivity/directives-class.spec.ts index c0ec77e14d0567..b7e085aba15143 100644 --- a/test/e2e/specs/interactivity/directives-class.spec.ts +++ b/test/e2e/specs/interactivity/directives-class.spec.ts @@ -98,4 +98,14 @@ test.describe( 'data-wp-class', () => { await page.getByTestId( 'toggle context false value' ).click(); await expect( el ).toHaveClass( '' ); } ); + + test( 'can use BEM notation classes', async ( { page } ) => { + const el = page.getByTestId( 'can use BEM notation classes' ); + await expect( el ).toHaveClass( 'block__element--modifier' ); + } ); + + test( 'can use classes with several dashes', async ( { page } ) => { + const el = page.getByTestId( 'can use classes with several dashes' ); + await expect( el ).toHaveClass( 'main-bg----color' ); + } ); } ); From de67122ae164f0c2f443953636fee5dd6d8c5601 Mon Sep 17 00:00:00 2001 From: Sarah Norris <1645628+mikachan@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:58:39 +0100 Subject: [PATCH 071/150] Footnotes: Add typography, dimensions, and border block supports (#53044) * Remove background and add link to default color controls * Add typography, dimensions, and border controls --- docs/reference-guides/core-blocks.md | 2 +- .../block-library/src/footnotes/block.json | 40 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index df34a04bcf5d73..0b323b38197792 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -275,7 +275,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb - **Name:** core/footnotes - **Category:** text -- **Supports:** color (background, link, text), ~~html~~, ~~multiple~~, ~~reusable~~ +- **Supports:** color (background, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** ## Classic diff --git a/packages/block-library/src/footnotes/block.json b/packages/block-library/src/footnotes/block.json index 6a94c42eb42e51..28b094f24f9164 100644 --- a/packages/block-library/src/footnotes/block.json +++ b/packages/block-library/src/footnotes/block.json @@ -9,16 +9,52 @@ "textdomain": "default", "usesContext": [ "postId", "postType" ], "supports": { + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": false, + "color": false, + "width": false, + "style": false + } + }, "color": { + "background": true, "link": true, + "text": true, "__experimentalDefaultControls": { - "background": true, + "link": true, "text": true } }, "html": false, "multiple": false, - "reusable": false + "reusable": false, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalWritingMode": true, + "__experimentalDefaultControls": { + "fontSize": true + } + } }, "style": "wp-block-footnotes" } From 16598fc7e3c5330b85fdee322f1f7155754a5e5e Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:03:48 +0900 Subject: [PATCH 072/150] Media & Text Block: Fix deprecation with `isStackOnMobile` default value changed (#49538) * Media & Text Block: Fixed deprecation with `isStackOnMobile` default value changed * Rename variable * Add fixture * Fix fixture html * Update test/integration/fixtures/blocks/core__media-text__deprecated-v2.html Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * Update fixture --------- Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> --- .../src/media-text/deprecated.js | 20 +++++++++----- .../core__media-text__deprecated-v2.html | 12 +++++++++ .../core__media-text__deprecated-v2.json | 27 +++++++++++++++++++ ...ore__media-text__deprecated-v2.parsed.json | 25 +++++++++++++++++ ..._media-text__deprecated-v2.serialized.html | 5 ++++ 5 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 test/integration/fixtures/blocks/core__media-text__deprecated-v2.html create mode 100644 test/integration/fixtures/blocks/core__media-text__deprecated-v2.json create mode 100644 test/integration/fixtures/blocks/core__media-text__deprecated-v2.parsed.json create mode 100644 test/integration/fixtures/blocks/core__media-text__deprecated-v2.serialized.html diff --git a/packages/block-library/src/media-text/deprecated.js b/packages/block-library/src/media-text/deprecated.js index 93366f827055ba..d03659022b95b4 100644 --- a/packages/block-library/src/media-text/deprecated.js +++ b/packages/block-library/src/media-text/deprecated.js @@ -76,7 +76,7 @@ const migrateDefaultAlign = ( attributes ) => { }; }; -const baseAttributes = { +const v0Attributes = { align: { type: 'string', default: 'wide', @@ -104,12 +104,16 @@ const baseAttributes = { }, isStackedOnMobile: { type: 'boolean', - default: true, + default: false, }, }; const v4ToV5BlockAttributes = { - ...baseAttributes, + ...v0Attributes, + isStackedOnMobile: { + type: 'boolean', + default: true, + }, mediaUrl: { type: 'string', source: 'attribute', @@ -578,7 +582,11 @@ const v4 = { // See: https://github.com/WordPress/gutenberg/pull/21169 const v3 = { attributes: { - ...baseAttributes, + ...v0Attributes, + isStackedOnMobile: { + type: 'boolean', + default: true, + }, backgroundColor: { type: 'string', }, @@ -726,7 +734,7 @@ const v3 = { // See: https://github.com/WordPress/gutenberg/pull/14364 const v2 = { attributes: { - ...baseAttributes, + ...v0Attributes, backgroundColor: { type: 'string', }, @@ -828,7 +836,7 @@ const v2 = { // See: https://github.com/WordPress/gutenberg/pull/11922 const v1 = { attributes: { - ...baseAttributes, + ...v0Attributes, backgroundColor: { type: 'string', }, diff --git a/test/integration/fixtures/blocks/core__media-text__deprecated-v2.html b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.html new file mode 100644 index 00000000000000..e258c04a8a8f83 --- /dev/null +++ b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.html @@ -0,0 +1,12 @@ + +
    +
    + +
    +
    + +

    My Content

    + +
    +
    + diff --git a/test/integration/fixtures/blocks/core__media-text__deprecated-v2.json b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.json new file mode 100644 index 00000000000000..ca2b0016872687 --- /dev/null +++ b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.json @@ -0,0 +1,27 @@ +[ + { + "name": "core/media-text", + "isValid": true, + "attributes": { + "align": "wide", + "mediaAlt": "", + "mediaPosition": "left", + "mediaType": "image", + "mediaWidth": 50, + "isStackedOnMobile": false, + "mediaUrl": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" + }, + "innerBlocks": [ + { + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": "My Content", + "dropCap": false, + "placeholder": "Content…" + }, + "innerBlocks": [] + } + ] + } +] diff --git a/test/integration/fixtures/blocks/core__media-text__deprecated-v2.parsed.json b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.parsed.json new file mode 100644 index 00000000000000..46b59fa2cac5f3 --- /dev/null +++ b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.parsed.json @@ -0,0 +1,25 @@ +[ + { + "blockName": "core/media-text", + "attrs": { + "mediaType": "image" + }, + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "placeholder": "Content…" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t

    My Content

    \n\t\t", + "innerContent": [ "\n\t\t

    My Content

    \n\t\t" ] + } + ], + "innerHTML": "\n
    \n\t
    \n\t\t\"\"/\n\t
    \n\t
    \n\t\t\n\t
    \n
    \n", + "innerContent": [ + "\n
    \n\t
    \n\t\t\"\"/\n\t
    \n\t
    \n\t\t", + null, + "\n\t
    \n
    \n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__media-text__deprecated-v2.serialized.html b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.serialized.html new file mode 100644 index 00000000000000..0c0fb613d64b53 --- /dev/null +++ b/test/integration/fixtures/blocks/core__media-text__deprecated-v2.serialized.html @@ -0,0 +1,5 @@ + +
    +

    My Content

    +
    + From 284455b1c4830c45ea0df749b801aab718c535dd Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Aug 2023 09:11:08 -0400 Subject: [PATCH 073/150] docs: Note the preios setup script (#53312) --- .../code/react-native/getting-started-react-native.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/contributors/code/react-native/getting-started-react-native.md b/docs/contributors/code/react-native/getting-started-react-native.md index 9b8ae44f0d8b18..93be2f088fdc55 100644 --- a/docs/contributors/code/react-native/getting-started-react-native.md +++ b/docs/contributors/code/react-native/getting-started-react-native.md @@ -28,6 +28,7 @@ Note that the commands described here should be run in the top-level directory o ```sh nvm install npm ci +npm run native preios ``` ## Run From c5cb220595c7f362b7da0529f7ffcaf1779fd49a Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 5 Aug 2023 01:34:02 +0900 Subject: [PATCH 074/150] Components: Remove unused components from `/ui` (#52953) * Components: Remove unused components from `/ui` * Update readme * Add changelog --- packages/components/CHANGELOG.md | 4 + packages/components/src/ui/README.md | 23 +- .../components/src/ui/control-group/README.md | 23 -- .../src/ui/control-group/component.js | 75 ----- .../src/ui/control-group/context.js | 8 - .../components/src/ui/control-group/hook.js | 93 ------ .../components/src/ui/control-group/index.js | 3 - .../components/src/ui/control-group/styles.js | 43 --- .../test/__snapshots__/index.js.snap | 51 ---- .../src/ui/control-group/test/index.js | 22 -- .../components/src/ui/control-group/types.ts | 29 -- .../components/src/ui/control-label/README.md | 22 -- .../src/ui/control-label/component.js | 37 --- .../components/src/ui/control-label/hook.js | 43 --- .../components/src/ui/control-label/index.js | 2 - .../src/ui/control-label/stories/index.js | 13 - .../components/src/ui/control-label/styles.js | 65 ----- .../test/__snapshots__/index.js.snap | 141 ---------- .../src/ui/control-label/test/index.js | 39 --- .../components/src/ui/control-label/types.ts | 9 - .../components/src/ui/form-group/README.md | 113 -------- .../src/ui/form-group/form-group-content.js | 59 ---- .../src/ui/form-group/form-group-context.js | 31 -- .../src/ui/form-group/form-group-help.js | 32 --- .../src/ui/form-group/form-group-label.js | 30 -- .../src/ui/form-group/form-group-styles.js | 8 - .../src/ui/form-group/form-group.js | 54 ---- .../components/src/ui/form-group/index.js | 4 - .../src/ui/form-group/stories/index.js | 44 --- .../test/__snapshots__/index.js.snap | 121 -------- .../src/ui/form-group/test/index.js | 82 ------ .../components/src/ui/form-group/types.ts | 32 --- .../src/ui/form-group/use-form-group.js | 53 ---- packages/components/src/ui/index.js | 5 - packages/components/src/ui/spinner/README.md | 31 -- .../components/src/ui/spinner/component.js | 75 ----- packages/components/src/ui/spinner/index.js | 1 - .../src/ui/spinner/stories/index.js | 19 -- packages/components/src/ui/spinner/styles.js | 109 ------- .../spinner/test/__snapshots__/index.js.snap | 265 ------------------ .../components/src/ui/spinner/test/index.js | 32 --- packages/components/src/ui/spinner/utils.js | 2 - 42 files changed, 7 insertions(+), 1940 deletions(-) delete mode 100644 packages/components/src/ui/control-group/README.md delete mode 100644 packages/components/src/ui/control-group/component.js delete mode 100644 packages/components/src/ui/control-group/context.js delete mode 100644 packages/components/src/ui/control-group/hook.js delete mode 100644 packages/components/src/ui/control-group/index.js delete mode 100644 packages/components/src/ui/control-group/styles.js delete mode 100644 packages/components/src/ui/control-group/test/__snapshots__/index.js.snap delete mode 100644 packages/components/src/ui/control-group/test/index.js delete mode 100644 packages/components/src/ui/control-group/types.ts delete mode 100644 packages/components/src/ui/control-label/README.md delete mode 100644 packages/components/src/ui/control-label/component.js delete mode 100644 packages/components/src/ui/control-label/hook.js delete mode 100644 packages/components/src/ui/control-label/index.js delete mode 100644 packages/components/src/ui/control-label/stories/index.js delete mode 100644 packages/components/src/ui/control-label/styles.js delete mode 100644 packages/components/src/ui/control-label/test/__snapshots__/index.js.snap delete mode 100644 packages/components/src/ui/control-label/test/index.js delete mode 100644 packages/components/src/ui/control-label/types.ts delete mode 100644 packages/components/src/ui/form-group/README.md delete mode 100644 packages/components/src/ui/form-group/form-group-content.js delete mode 100644 packages/components/src/ui/form-group/form-group-context.js delete mode 100644 packages/components/src/ui/form-group/form-group-help.js delete mode 100644 packages/components/src/ui/form-group/form-group-label.js delete mode 100644 packages/components/src/ui/form-group/form-group-styles.js delete mode 100644 packages/components/src/ui/form-group/form-group.js delete mode 100644 packages/components/src/ui/form-group/index.js delete mode 100644 packages/components/src/ui/form-group/stories/index.js delete mode 100644 packages/components/src/ui/form-group/test/__snapshots__/index.js.snap delete mode 100644 packages/components/src/ui/form-group/test/index.js delete mode 100644 packages/components/src/ui/form-group/types.ts delete mode 100644 packages/components/src/ui/form-group/use-form-group.js delete mode 100644 packages/components/src/ui/index.js delete mode 100644 packages/components/src/ui/spinner/README.md delete mode 100644 packages/components/src/ui/spinner/component.js delete mode 100644 packages/components/src/ui/spinner/index.js delete mode 100644 packages/components/src/ui/spinner/stories/index.js delete mode 100644 packages/components/src/ui/spinner/styles.js delete mode 100644 packages/components/src/ui/spinner/test/__snapshots__/index.js.snap delete mode 100644 packages/components/src/ui/spinner/test/index.js delete mode 100644 packages/components/src/ui/spinner/utils.js diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 1fbb88ed83f941..a6fc7e845a4f9f 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -17,6 +17,10 @@ - `Modal`: Fix loss of focus when clicking outside ([#52653](https://github.com/WordPress/gutenberg/pull/52653)). +### Internal + +- `ControlGroup`, `FormGroup`, `ControlLabel`, `Spinner`: Remove unused `ui/` components from the codebase ([#52953](https://github.com/WordPress/gutenberg/pull/52953)). + ## 25.4.0 (2023-07-20) ### Enhancements diff --git a/packages/components/src/ui/README.md b/packages/components/src/ui/README.md index 4fdf6dd220f563..6099fe0032851f 100644 --- a/packages/components/src/ui/README.md +++ b/packages/components/src/ui/README.md @@ -1,22 +1,5 @@ -# (Next) Component System +# TODO -This directory contains the code for next Component System. +We want to get rid of this folder. Don't add anything new here. -More information can be found in the original GitHub issue: -https://github.com/WordPress/gutenberg/issues/27484 - -## Usage - -(This is still very much a work in progress) - -```jsx -import { Text, View } from '@wordpress/components/ui'; - -function Example() { - return ( - - Code is Poetry - - ); -} -``` +What is left of this folder should either be deleted (if unused elsewhere), or be moved into the root `packages/components/src` and `packages/components/src/utils` folders. \ No newline at end of file diff --git a/packages/components/src/ui/control-group/README.md b/packages/components/src/ui/control-group/README.md deleted file mode 100644 index 122a6a612a32c1..00000000000000 --- a/packages/components/src/ui/control-group/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# ControlGroup - -
    -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -
    - -`ControlGroup` is a layout-based component for rendering a group of control-based components, such as `Button`, `Select` or `TextInput`. Control components that render within `ControlGroup` automatically have their borders offset and border-radii rounded. - -## Usage - -```jsx -import { Button, ControlGroup, Select, TextInput } from `@wordpress/components/ui` - -function Example() { - return ( - - - * - * - -
    -
    -`; diff --git a/packages/components/src/ui/control-group/test/index.js b/packages/components/src/ui/control-group/test/index.js deleted file mode 100644 index 0587c329b40569..00000000000000 --- a/packages/components/src/ui/control-group/test/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * External dependencies - */ -import { render } from '@testing-library/react'; - -/** - * Internal dependencies - */ -import { ControlGroup } from '..'; -import Button from '../../../button'; - -describe( 'props', () => { - test( 'should render correctly', () => { - const { container } = render( - - - - - ); - expect( container ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/components/src/ui/control-group/types.ts b/packages/components/src/ui/control-group/types.ts deleted file mode 100644 index c7211eb35283a5..00000000000000 --- a/packages/components/src/ui/control-group/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * External dependencies - */ -import type { CSSProperties } from 'react'; - -/** - * Internal dependencies - */ -import type { FlexProps } from '../../flex/types'; - -export type ControlGroupContext = { - isFirst?: boolean; - isLast?: boolean; - isMiddle?: boolean; - isOnly?: boolean; - isVertical?: boolean; - styles?: string; -}; - -export type Props = Pick< FlexProps, 'direction' > & { - /** - * Adjust the layout (width) of content using CSS grid (`grid-template-columns`). - */ - templateColumns?: CSSProperties[ 'gridTemplateColumns' ]; - /** - * The children elements. - */ - children: React.ReactNode; -}; diff --git a/packages/components/src/ui/control-label/README.md b/packages/components/src/ui/control-label/README.md deleted file mode 100644 index d4e31e034dfd80..00000000000000 --- a/packages/components/src/ui/control-label/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# ControlLabel - -
    -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -
    - -`ControlLabel` is a form component that works with `FormGroup` to provide a label for form elements (e.g. `Switch` or `TextInput`). - -## Usage - -```jsx -import { ControlLabel, FormGroup, TextInput } from '@wordpress/components/ui'; - -function Example() { - return ( - - First Name - - - ); -} -``` diff --git a/packages/components/src/ui/control-label/component.js b/packages/components/src/ui/control-label/component.js deleted file mode 100644 index 62a3e8a790c68b..00000000000000 --- a/packages/components/src/ui/control-label/component.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Internal dependencies - */ -import { contextConnect } from '../context'; -import { View } from '../../view'; -import { useControlLabel } from './hook'; - -/** - * @param {import('../context').WordPressComponentProps} props - * @param {import('react').ForwardedRef} forwardedRef - */ -function ControlLabel( props, forwardedRef ) { - const controlLabelProps = useControlLabel( props ); - - return ; -} - -/** - * `ControlLabel` is a form component that works with `FormGroup` to provide a - * label for form elements (e.g. `Switch` or `TextInput`). - * - * ```jsx - * import { ControlLabel, FormGroup, TextInput } from '@wordpress/components/ui'; - * - * function Example() { - * return ( - * - * First Name - * - * - * ); - * } - * ``` - */ -const ConnectedControlLabel = contextConnect( ControlLabel, 'ControlLabel' ); - -export default ConnectedControlLabel; diff --git a/packages/components/src/ui/control-label/hook.js b/packages/components/src/ui/control-label/hook.js deleted file mode 100644 index dcdd5bb7e7effc..00000000000000 --- a/packages/components/src/ui/control-label/hook.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Internal dependencies - */ -import { useContextSystem } from '../context'; -import { useFormGroupContextId } from '../form-group'; -import { useText } from '../../text'; -import * as styles from './styles'; -import { useCx } from '../../utils/hooks/use-cx'; - -/** - * @param {import('../context').WordPressComponentProps} props - */ -export function useControlLabel( props ) { - const { - htmlFor: htmlForProp, - isBlock = false, - size = 'medium', - truncate = true, - ...otherProps - } = useContextSystem( props, 'ControlLabel' ); - - const { className, ...textProps } = useText( { - ...otherProps, - isBlock, - truncate, - } ); - - const cx = useCx(); - - const htmlFor = useFormGroupContextId( htmlForProp ); - const classes = cx( - styles.ControlLabel, - styles[ /** @type {'small' | 'medium' | 'large'} */ ( size ) ], - className, - isBlock ? styles.block : styles.inline - ); - - return { - ...textProps, - className: classes, - htmlFor, - }; -} diff --git a/packages/components/src/ui/control-label/index.js b/packages/components/src/ui/control-label/index.js deleted file mode 100644 index c73e0027014e24..00000000000000 --- a/packages/components/src/ui/control-label/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as ControlLabel } from './component'; -export * from './hook'; diff --git a/packages/components/src/ui/control-label/stories/index.js b/packages/components/src/ui/control-label/stories/index.js deleted file mode 100644 index e22d46cbf70198..00000000000000 --- a/packages/components/src/ui/control-label/stories/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { ControlLabel } from '../index'; - -export default { - component: ControlLabel, - title: 'Components (Experimental)/ControlLabel', -}; - -export const _default = () => { - return Label; -}; diff --git a/packages/components/src/ui/control-label/styles.js b/packages/components/src/ui/control-label/styles.js deleted file mode 100644 index 8f96e91d0ef3fb..00000000000000 --- a/packages/components/src/ui/control-label/styles.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * External dependencies - */ -import { css } from '@emotion/react'; - -/** - * Internal dependencies - */ -import CONFIG from '../../utils/config-values'; -import { getHighDpi } from '../utils/get-high-dpi'; - -const lineHeight = `calc(${ CONFIG.fontSize } * 1.2)`; - -/** - * @param {keyof CONFIG} size The padding size. - */ -function getPadding( size ) { - return `calc((${ CONFIG[ size ] } - ${ lineHeight }) / 2)`; -} - -const highDpiAdjust = getHighDpi` - > * { - position: relative; - top: 0.5px; - } -`; - -export const ControlLabel = css` - display: inline-block; - line-height: ${ lineHeight }; - margin: 0; - max-width: 100%; - padding-bottom: ${ getPadding( 'controlHeight' ) }; - padding-top: ${ getPadding( 'controlHeight' ) }; - - &:active { - user-select: none; - } - - ${ highDpiAdjust }; -`; - -export const large = css` - padding-bottom: ${ getPadding( 'controlHeightLarge' ) }; - padding-top: ${ getPadding( 'controlHeightLarge' ) }; -`; - -export const medium = css` - padding-bottom: ${ getPadding( 'controlHeight' ) }; - padding-top: ${ getPadding( 'controlHeight' ) }; -`; - -export const small = css` - padding-bottom: ${ getPadding( 'controlHeightSmall' ) }; - padding-top: ${ getPadding( 'controlHeightSmall' ) }; -`; - -export const inline = css` - display: inline-block; - vertical-align: middle; -`; - -export const block = css` - display: block; -`; diff --git a/packages/components/src/ui/control-label/test/__snapshots__/index.js.snap b/packages/components/src/ui/control-label/test/__snapshots__/index.js.snap deleted file mode 100644 index ffe2d42932d002..00000000000000 --- a/packages/components/src/ui/control-label/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`props should render correctly 1`] = ` -.emotion-0 { - display: inline-block; - line-height: calc(13px * 1.2); - margin: 0; - max-width: 100%; - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: #1e1e1e; - line-height: 1.2; - margin: 0; - font-size: calc((13 / 13) * 13px); - font-weight: normal; - display: inline-block; - vertical-align: middle; -} - -.emotion-0:active { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media ( -webkit-min-device-pixel-ratio: 1.25 ), ( min-resolution: 120dpi ) { - .emotion-0>* { - position: relative; - top: 0.5px; - } -} - -
    - -
    -`; - -exports[`props should render no truncate 1`] = ` -.emotion-0 { - display: inline-block; - line-height: calc(13px * 1.2); - margin: 0; - max-width: 100%; - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - color: #1e1e1e; - line-height: 1.2; - margin: 0; - font-size: calc((13 / 13) * 13px); - font-weight: normal; - display: inline-block; - vertical-align: middle; -} - -.emotion-0:active { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media ( -webkit-min-device-pixel-ratio: 1.25 ), ( min-resolution: 120dpi ) { - .emotion-0>* { - position: relative; - top: 0.5px; - } -} - -
    - -
    -`; - -exports[`props should render size 1`] = ` -.emotion-0 { - display: inline-block; - line-height: calc(13px * 1.2); - margin: 0; - max-width: 100%; - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - padding-bottom: calc((calc( 36px * 0.8 ) - calc(13px * 1.2)) / 2); - padding-top: calc((calc( 36px * 0.8 ) - calc(13px * 1.2)) / 2); - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: #1e1e1e; - line-height: 1.2; - margin: 0; - font-size: calc((13 / 13) * 13px); - font-weight: normal; - display: inline-block; - vertical-align: middle; -} - -.emotion-0:active { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media ( -webkit-min-device-pixel-ratio: 1.25 ), ( min-resolution: 120dpi ) { - .emotion-0>* { - position: relative; - top: 0.5px; - } -} - -
    - -
    -`; diff --git a/packages/components/src/ui/control-label/test/index.js b/packages/components/src/ui/control-label/test/index.js deleted file mode 100644 index 4443457a566f9d..00000000000000 --- a/packages/components/src/ui/control-label/test/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * External dependencies - */ -import { render, screen } from '@testing-library/react'; - -/** - * Internal dependencies - */ -import { ControlLabel } from '../index'; - -describe( 'props', () => { - test( 'should render correctly', () => { - const { container } = render( Label ); - - expect( container ).toMatchSnapshot(); - } ); - - test( 'should render htmlFor', () => { - render( Label ); - - expect( screen.getByText( 'Label' ) ).toHaveAttribute( 'for', 'Field' ); - } ); - - test( 'should render size', () => { - const { container } = render( - Label - ); - - expect( container ).toMatchSnapshot(); - } ); - - test( 'should render no truncate', () => { - const { container } = render( - Label - ); - - expect( container ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/components/src/ui/control-label/types.ts b/packages/components/src/ui/control-label/types.ts deleted file mode 100644 index e76b7d3d895e73..00000000000000 --- a/packages/components/src/ui/control-label/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Internal dependencies - */ -import type { Props as TextProps } from '../../text/types'; - -export type Props = TextProps & { - isBlock?: boolean; - size?: 'large' | 'medium' | 'small'; -}; diff --git a/packages/components/src/ui/form-group/README.md b/packages/components/src/ui/form-group/README.md deleted file mode 100644 index fec4316caf3553..00000000000000 --- a/packages/components/src/ui/form-group/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# FormGroup - -
    -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -
    - -`FormGroup` is a form component that groups a label with a form element (e.g. `Switch` or `TextInput`). - -## Usage - -```jsx -import { FormGroup, TextInput } from '@wordpress/components/ui'; - -function Example() { - return ( - - - - ); -} -``` - -## Props - -##### align - -**Type**: `CSS['alignItems']` - -Adjusts the block alignment of children. - -##### alignLabel - -**Type**: `Pick` - -Aligns the label within `FormGroup`. - -##### alignment - -**Type**: `string` - -Adjusts the horizontal and vertical alignment of children. - -##### columns - -**Type**: `number`,`(number`,`null)[]` - -Adjusts the number of columns of the `Grid`. - -##### gap - -**Type**: `number` - -Gap between each child. - -##### help - -**Type**: `React.ReactElement` - -Displays help content. - -##### horizontal - -**Type**: `boolean` - -Displays the label and form field horizontally. - -##### isInline - -**Type**: `boolean` - -Changes the CSS display from `grid` to `inline-grid`. - -##### justify - -**Type**: `CSS['justifyContent']` - -Adjusts the inline alignment of children. - -##### label - -**Type**: `string` - -Label of the form field. - -##### labelHidden - -**Type**: `boolean` - -Visually hides the label. - -##### rows - -**Type**: `number`,`(number`,`null)[]` - -Adjusts the number of rows of the `Grid`. - -##### templateColumns - -**Type**: `CSS['gridTemplateColumns']` - -Adjusts the CSS grid `template-columns`. - -##### templateRows - -**Type**: `CSS['gridTemplateRows']` - -Adjusts the CSS grid `template-rows`. - -##### truncate - -**Type**: `boolean` - -Truncates the label text content. diff --git a/packages/components/src/ui/form-group/form-group-content.js b/packages/components/src/ui/form-group/form-group-content.js deleted file mode 100644 index a9bdc87ec1b175..00000000000000 --- a/packages/components/src/ui/form-group/form-group-content.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo, memo } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { VStack } from '../../v-stack'; -import { FormGroupContext } from './form-group-context'; -import FormGroupHelp from './form-group-help'; -import FormGroupLabel from './form-group-label'; - -/** - * @param {import('../context').WordPressComponentProps} props - */ -function FormGroupContent( { - alignLabel, - children, - help, - horizontal = false, - id, - label, - labelHidden, - spacing = 2, - truncate, - ...props -} ) { - const contextProps = useMemo( - () => ( { id, horizontal } ), - [ id, horizontal ] - ); - - const content = help ? ( - - { children } - { help } - - ) : ( - children - ); - - return ( - - - { label } - - { content } - - ); -} - -export default memo( FormGroupContent ); diff --git a/packages/components/src/ui/form-group/form-group-context.js b/packages/components/src/ui/form-group/form-group-context.js deleted file mode 100644 index e98f93de5095ac..00000000000000 --- a/packages/components/src/ui/form-group/form-group-context.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * WordPress dependencies - */ -import { createContext, useContext } from '@wordpress/element'; - -/** - * @typedef {{ id?: import('react').ReactText, horizontal: boolean }} FormGroupContext - */ - -/** - * @type {FormGroupContext} - */ -const initialContext = { - id: undefined, - horizontal: true, -}; - -/** - * @type {import('react').Context} - */ -export const FormGroupContext = createContext( initialContext ); -export const useFormGroupContext = () => useContext( FormGroupContext ); - -/** - * @param {string | undefined} id The preferred id for the form group element. - * @return {import('react').ReactText | undefined} The form group context id. - */ -export const useFormGroupContextId = ( id ) => { - const contextId = useFormGroupContext().id; - return id || contextId; -}; diff --git a/packages/components/src/ui/form-group/form-group-help.js b/packages/components/src/ui/form-group/form-group-help.js deleted file mode 100644 index 456d5325b5cbb0..00000000000000 --- a/packages/components/src/ui/form-group/form-group-help.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { memo } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { ContextSystemProvider } from '../context'; - -const contextValue = { Text: { variant: 'muted' } }; - -/** - * @typedef Props - * @property {import('react').ReactNode} [children] The content to display within `FormGroupHelp`. - */ - -/** - * - * @param {Props} props - */ -function FormGroupHelp( { children } ) { - if ( ! children ) return null; - - return ( - - { children } - - ); -} - -export default memo( FormGroupHelp ); diff --git a/packages/components/src/ui/form-group/form-group-label.js b/packages/components/src/ui/form-group/form-group-label.js deleted file mode 100644 index 21a4a2dda5b129..00000000000000 --- a/packages/components/src/ui/form-group/form-group-label.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * WordPress dependencies - */ -import { memo } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { ControlLabel } from '../control-label'; -import { VisuallyHidden } from '../../visually-hidden'; - -/** - * @param {import('../context').WordPressComponentProps} props - * @return {JSX.Element | null} The form group's label. - */ -function FormGroupLabel( { children, id, labelHidden = false, ...props } ) { - if ( ! children ) return null; - - if ( labelHidden ) { - return ( - - { children } - - ); - } - - return { children }; -} - -export default memo( FormGroupLabel ); diff --git a/packages/components/src/ui/form-group/form-group-styles.js b/packages/components/src/ui/form-group/form-group-styles.js deleted file mode 100644 index b675565d243562..00000000000000 --- a/packages/components/src/ui/form-group/form-group-styles.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * External dependencies - */ -import { css } from '@emotion/react'; - -export const FormGroup = css` - width: 100%; -`; diff --git a/packages/components/src/ui/form-group/form-group.js b/packages/components/src/ui/form-group/form-group.js deleted file mode 100644 index 79881be46f7116..00000000000000 --- a/packages/components/src/ui/form-group/form-group.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Internal dependencies - */ -import { contextConnect } from '../context'; -import { Grid } from '../../grid'; -import { View } from '../../view'; -import FormGroupContent from './form-group-content'; -import { useFormGroup } from './use-form-group'; - -/** - * @param {import('../context').WordPressComponentProps} props - * @param {import('react').ForwardedRef} forwardedRef - */ -function FormGroup( props, forwardedRef ) { - const { contentProps, horizontal, ...otherProps } = useFormGroup( props ); - - if ( horizontal ) { - return ( - - - - ); - } - - return ( - - - - ); -} - -/** - * `FormGroup` is a form component that groups a label with a form element (e.g. `Switch` or `TextInput`). - * - * @example - * ```jsx - * import { FormGroup, TextInput } from `@wordpress/components/ui`; - * - * function Example() { - * return ( - * - * - * - * ); - * } - * ``` - */ -const ConnectedFormGroup = contextConnect( FormGroup, 'FormGroup' ); - -export default ConnectedFormGroup; diff --git a/packages/components/src/ui/form-group/index.js b/packages/components/src/ui/form-group/index.js deleted file mode 100644 index 64c29381529b28..00000000000000 --- a/packages/components/src/ui/form-group/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { default as FormGroup } from './form-group'; - -export * from './use-form-group'; -export * from './form-group-context'; diff --git a/packages/components/src/ui/form-group/stories/index.js b/packages/components/src/ui/form-group/stories/index.js deleted file mode 100644 index 804a41ad265f26..00000000000000 --- a/packages/components/src/ui/form-group/stories/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Internal dependencies - */ -import { FormGroup, useFormGroupContextId } from '../index'; -import { Text } from '../../../text'; - -// @todo: Refactor this after adding next TextInput component. -const TextInput = ( { id: idProp, ...props } ) => { - const id = useFormGroupContextId( idProp ); - return ( -
    - -
    - ); -}; - -export default { - component: FormGroup, - title: 'Components (Experimental)/FormGroup', -}; - -export const _default = () => { - return ( - - - - ); -}; - -export const _labelHidden = () => { - return ( - - - - ); -}; - -export const _help = () => { - return ( - Help Text } label="Form Group"> - - - ); -}; diff --git a/packages/components/src/ui/form-group/test/__snapshots__/index.js.snap b/packages/components/src/ui/form-group/test/__snapshots__/index.js.snap deleted file mode 100644 index b10a44c09d5d8b..00000000000000 --- a/packages/components/src/ui/form-group/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,121 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`props should render alignLabel 1`] = ` -.emotion-0 { - width: 100%; -} - -.emotion-2 { - display: inline-block; - line-height: calc(13px * 1.2); - margin: 0; - max-width: 100%; - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - color: #1e1e1e; - line-height: 1.2; - margin: 0; - font-size: calc((13 / 13) * 13px); - font-weight: normal; - text-align: right; - display: inline-block; - vertical-align: middle; -} - -.emotion-2:active { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media ( -webkit-min-device-pixel-ratio: 1.25 ), ( min-resolution: 120dpi ) { - .emotion-2>* { - position: relative; - top: 0.5px; - } -} - -
    -
    - - -
    -
    -`; - -exports[`props should render vertically 1`] = ` -.emotion-0 { - width: 100%; -} - -.emotion-2 { - display: inline-block; - line-height: calc(13px * 1.2); - margin: 0; - max-width: 100%; - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - padding-bottom: calc((36px - calc(13px * 1.2)) / 2); - padding-top: calc((36px - calc(13px * 1.2)) / 2); - color: #1e1e1e; - line-height: 1.2; - margin: 0; - font-size: calc((13 / 13) * 13px); - font-weight: normal; - text-align: left; - display: inline-block; - vertical-align: middle; -} - -.emotion-2:active { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media ( -webkit-min-device-pixel-ratio: 1.25 ), ( min-resolution: 120dpi ) { - .emotion-2>* { - position: relative; - top: 0.5px; - } -} - -
    -
    - - -
    -
    -`; diff --git a/packages/components/src/ui/form-group/test/index.js b/packages/components/src/ui/form-group/test/index.js deleted file mode 100644 index b6bfcd4d8f2110..00000000000000 --- a/packages/components/src/ui/form-group/test/index.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * External dependencies - */ -import { render, screen } from '@testing-library/react'; - -/** - * Internal dependencies - */ -import { ControlLabel } from '../../control-label'; -import { FormGroup, useFormGroupContextId } from '../index'; - -// @todo: Refactor this after adding next TextInput component. -const TextInput = ( { id: idProp, ...props } ) => { - const id = useFormGroupContextId( idProp ); - return ; -}; - -// We're intentionally using a string literal for the ID to ensure -// the htmlFor and id properties of the label and inputs are bound correctly. -/* eslint-disable no-restricted-syntax */ -describe( 'props', () => { - test( 'should render correctly', () => { - render( - - - - ); - - expect( - screen.getByRole( 'textbox', { name: 'First name' } ) - ).toBeVisible(); - } ); - - test( 'should render label without prop correctly', () => { - render( - - First name - - - ); - - expect( - screen.getByRole( 'textbox', { name: 'First name' } ) - ).toBeVisible(); - } ); - - test( 'should render labelHidden', () => { - render( - - - - ); - - expect( - screen.getByRole( 'textbox', { name: 'First name' } ) - ).toBeVisible(); - expect( screen.getByText( 'First name' ) ).toHaveClass( - 'components-visually-hidden' - ); - } ); - - test( 'should render alignLabel', () => { - const { container } = render( - - - - ); - - expect( container ).toMatchSnapshot(); - } ); - - test( 'should render vertically', () => { - const { container } = render( - - - - ); - - expect( container ).toMatchSnapshot(); - } ); -} ); -/* eslint-enable no-restricted-syntax */ diff --git a/packages/components/src/ui/form-group/types.ts b/packages/components/src/ui/form-group/types.ts deleted file mode 100644 index 288acfdba7e171..00000000000000 --- a/packages/components/src/ui/form-group/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * External dependencies - */ -import type { CSSProperties, ReactNode, ReactText } from 'react'; - -/** - * Internal dependencies - */ -import type { Props as ControlLabelProps } from '../control-label/types'; -import type { GridProps } from '../../grid/types'; - -export type FormGroupLabelProps = ControlLabelProps & { - labelHidden?: boolean; - id?: ReactText; -}; - -export type FormGroupContentProps = FormGroupLabelProps & { - alignLabel?: CSSProperties[ 'textAlign' ]; - help?: ReactNode; - horizontal?: boolean; - label?: ReactNode; - spacing?: CSSProperties[ 'width' ]; - truncate?: boolean; -}; - -type Horizontal = GridProps & { - horizontal: true; -}; - -type Vertical = { horizontal: false }; - -export type FormGroupProps = FormGroupContentProps & ( Horizontal | Vertical ); diff --git a/packages/components/src/ui/form-group/use-form-group.js b/packages/components/src/ui/form-group/use-form-group.js deleted file mode 100644 index eebc5ae3506398..00000000000000 --- a/packages/components/src/ui/form-group/use-form-group.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * WordPress dependencies - */ -import { useInstanceId } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import { useContextSystem } from '../context'; -import * as styles from './form-group-styles'; -import { useCx } from '../../utils/hooks/use-cx'; - -/** - * @param {import('../context').WordPressComponentProps} props - */ -export function useFormGroup( props ) { - const { - alignLabel = 'left', - children, - className, - help, - horizontal = false, - id: idProp, - label, - labelHidden = false, - truncate = false, - ...otherProps - } = useContextSystem( props, 'FormGroup' ); - - const id = useInstanceId( useFormGroup, 'form-group', idProp ); - - const cx = useCx(); - - const classes = cx( styles.FormGroup, className ); - - const contentProps = { - alignLabel, - children, - help, - id, - horizontal, - label, - labelHidden, - truncate, - }; - - return { - ...otherProps, - className: classes, - contentProps, - horizontal, - }; -} diff --git a/packages/components/src/ui/index.js b/packages/components/src/ui/index.js deleted file mode 100644 index e8587d84b298ad..00000000000000 --- a/packages/components/src/ui/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export * from './control-group'; -export * from './control-label'; -export * from './form-group'; -export * from './shortcut'; -export * from './spinner'; diff --git a/packages/components/src/ui/spinner/README.md b/packages/components/src/ui/spinner/README.md deleted file mode 100644 index 5e25f9992d0c32..00000000000000 --- a/packages/components/src/ui/spinner/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Spinner - -
    -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -
    - -`Spinner` is a component that notify users that their action is being processed. - -## Usage - -```jsx -import { Spinner } from '@wordpress/components/ui'; - -function Example() { - return ; -} -``` - -## Props - -##### color - -**Type**: `CSSProperties['color']` - -The color of the `Spinner`. - -##### size - -**Type**: `number` - -The size of the `Spinner`. diff --git a/packages/components/src/ui/spinner/component.js b/packages/components/src/ui/spinner/component.js deleted file mode 100644 index d8da4fcf708231..00000000000000 --- a/packages/components/src/ui/spinner/component.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Internal dependencies - */ -import { BarsView, BarsWrapperView, ContainerView } from './styles'; -import { BASE_SIZE, WRAPPER_SIZE } from './utils'; -import { contextConnect, useContextSystem } from '../context'; -import { COLORS } from '../../utils/colors-values'; - -/* eslint-disable jsdoc/valid-types */ -/** - * @typedef Props - * @property {import('react').CSSProperties['color']} [color] Color of `Spinner`. - * @property {number} [size=16] Size of `Spinner`. - */ -/* eslint-enable jsdoc/valid-types */ - -/** - * - * @param {import('../context').WordPressComponentProps} props - * @param {import('react').ForwardedRef} forwardedRef - */ -function Spinner( props, forwardedRef ) { - const { - color = COLORS.gray[ 900 ], - size = BASE_SIZE, - ...otherProps - } = useContextSystem( props, 'Spinner' ); - const ratio = size / BASE_SIZE; - const scale = ( ratio * BASE_SIZE ) / WRAPPER_SIZE; - const transform = `scale(${ scale })`; - - const styles = { transform }; - - return ( - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - ); -} - -/** - * `Spinner` is a component that notify users that their action is being processed. - * - * @example - * ```jsx - * import { Spinner } from `@wordpress/components/ui`; - * - * function Example() { - * return ( - * - * ); - * } - * ``` - */ -export default contextConnect( Spinner, 'Spinner' ); diff --git a/packages/components/src/ui/spinner/index.js b/packages/components/src/ui/spinner/index.js deleted file mode 100644 index 84ac37f7401d54..00000000000000 --- a/packages/components/src/ui/spinner/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as Spinner } from './component'; diff --git a/packages/components/src/ui/spinner/stories/index.js b/packages/components/src/ui/spinner/stories/index.js deleted file mode 100644 index 9cb006d883c1ee..00000000000000 --- a/packages/components/src/ui/spinner/stories/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Internal dependencies - */ -import { Spinner } from '../index'; - -export default { - component: Spinner, - title: 'Components (Experimental)/Spinner', -}; - -export const _default = () => { - return ( - <> - - - - - ); -}; diff --git a/packages/components/src/ui/spinner/styles.js b/packages/components/src/ui/spinner/styles.js deleted file mode 100644 index 057cca34c68460..00000000000000 --- a/packages/components/src/ui/spinner/styles.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * External dependencies - */ -import styled from '@emotion/styled'; - -/** - * Internal dependencies - */ -import { WRAPPER_SIZE } from './utils'; - -export const ContainerView = styled.div` - display: flex; - pointer-events: none; - position: relative; -`; - -export const BarsWrapperView = styled.div` - height: ${ WRAPPER_SIZE }px; - left: 0; - opacity: 0.6; - position: absolute; - top: 0; - transform-origin: top left; - width: ${ WRAPPER_SIZE }px; -`; - -export const BarsView = styled.div` - color: currentColor; - display: inline-flex; - height: 54px; - left: 50%; - padding: 10px; - position: absolute; - top: 50%; - transform: translate( -50%, -50% ); - width: 54px; - - > div { - animation: ComponentsUISpinnerFadeAnimation 1000ms linear infinite; - background: currentColor; - border-radius: 50px; - height: 16%; - left: 49%; - opacity: 0; - position: absolute; - top: 43%; - width: 6%; - } - - @keyframes ComponentsUISpinnerFadeAnimation { - from { - opacity: 1; - } - to { - opacity: 0.25; - } - } - - .InnerBar1 { - animation-delay: 0s; - transform: rotate( 0deg ) translate( 0, -130% ); - } - - .InnerBar2 { - animation-delay: -0.9167s; - transform: rotate( 30deg ) translate( 0, -130% ); - } - - .InnerBar3 { - animation-delay: -0.833s; - transform: rotate( 60deg ) translate( 0, -130% ); - } - .InnerBar4 { - animation-delay: -0.7497s; - transform: rotate( 90deg ) translate( 0, -130% ); - } - .InnerBar5 { - animation-delay: -0.667s; - transform: rotate( 120deg ) translate( 0, -130% ); - } - .InnerBar6 { - animation-delay: -0.5837s; - transform: rotate( 150deg ) translate( 0, -130% ); - } - .InnerBar7 { - animation-delay: -0.5s; - transform: rotate( 180deg ) translate( 0, -130% ); - } - .InnerBar8 { - animation-delay: -0.4167s; - transform: rotate( 210deg ) translate( 0, -130% ); - } - .InnerBar9 { - animation-delay: -0.333s; - transform: rotate( 240deg ) translate( 0, -130% ); - } - .InnerBar10 { - animation-delay: -0.2497s; - transform: rotate( 270deg ) translate( 0, -130% ); - } - .InnerBar11 { - animation-delay: -0.167s; - transform: rotate( 300deg ) translate( 0, -130% ); - } - .InnerBar12 { - animation-delay: -0.0833s; - transform: rotate( 330deg ) translate( 0, -130% ); - } -`; diff --git a/packages/components/src/ui/spinner/test/__snapshots__/index.js.snap b/packages/components/src/ui/spinner/test/__snapshots__/index.js.snap deleted file mode 100644 index 70fd3df3a5b484..00000000000000 --- a/packages/components/src/ui/spinner/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,265 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`props should render color 1`] = ` -Snapshot Diff: -- First value -+ Second value - -@@ -11,11 +11,11 @@ - class="css-1rq9ofd-BarsWrapperView e1s9yo7h1" - style="transform: scale(0.4444444444444444);" - > -
    -
    -
    div { - -webkit-animation: ComponentsUISpinnerFadeAnimation 1000ms linear infinite; - animation: ComponentsUISpinnerFadeAnimation 1000ms linear infinite; - background: currentColor; - border-radius: 50px; - height: 16%; - left: 49%; - opacity: 0; - position: absolute; - top: 43%; - width: 6%; -} - -.emotion-4 .InnerBar1 { - -webkit-animation-delay: 0s; - animation-delay: 0s; - -webkit-transform: rotate( 0deg ) translate( 0, -130% ); - -moz-transform: rotate( 0deg ) translate( 0, -130% ); - -ms-transform: rotate( 0deg ) translate( 0, -130% ); - transform: rotate( 0deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar2 { - -webkit-animation-delay: -0.9167s; - animation-delay: -0.9167s; - -webkit-transform: rotate( 30deg ) translate( 0, -130% ); - -moz-transform: rotate( 30deg ) translate( 0, -130% ); - -ms-transform: rotate( 30deg ) translate( 0, -130% ); - transform: rotate( 30deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar3 { - -webkit-animation-delay: -0.833s; - animation-delay: -0.833s; - -webkit-transform: rotate( 60deg ) translate( 0, -130% ); - -moz-transform: rotate( 60deg ) translate( 0, -130% ); - -ms-transform: rotate( 60deg ) translate( 0, -130% ); - transform: rotate( 60deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar4 { - -webkit-animation-delay: -0.7497s; - animation-delay: -0.7497s; - -webkit-transform: rotate( 90deg ) translate( 0, -130% ); - -moz-transform: rotate( 90deg ) translate( 0, -130% ); - -ms-transform: rotate( 90deg ) translate( 0, -130% ); - transform: rotate( 90deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar5 { - -webkit-animation-delay: -0.667s; - animation-delay: -0.667s; - -webkit-transform: rotate( 120deg ) translate( 0, -130% ); - -moz-transform: rotate( 120deg ) translate( 0, -130% ); - -ms-transform: rotate( 120deg ) translate( 0, -130% ); - transform: rotate( 120deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar6 { - -webkit-animation-delay: -0.5837s; - animation-delay: -0.5837s; - -webkit-transform: rotate( 150deg ) translate( 0, -130% ); - -moz-transform: rotate( 150deg ) translate( 0, -130% ); - -ms-transform: rotate( 150deg ) translate( 0, -130% ); - transform: rotate( 150deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar7 { - -webkit-animation-delay: -0.5s; - animation-delay: -0.5s; - -webkit-transform: rotate( 180deg ) translate( 0, -130% ); - -moz-transform: rotate( 180deg ) translate( 0, -130% ); - -ms-transform: rotate( 180deg ) translate( 0, -130% ); - transform: rotate( 180deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar8 { - -webkit-animation-delay: -0.4167s; - animation-delay: -0.4167s; - -webkit-transform: rotate( 210deg ) translate( 0, -130% ); - -moz-transform: rotate( 210deg ) translate( 0, -130% ); - -ms-transform: rotate( 210deg ) translate( 0, -130% ); - transform: rotate( 210deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar9 { - -webkit-animation-delay: -0.333s; - animation-delay: -0.333s; - -webkit-transform: rotate( 240deg ) translate( 0, -130% ); - -moz-transform: rotate( 240deg ) translate( 0, -130% ); - -ms-transform: rotate( 240deg ) translate( 0, -130% ); - transform: rotate( 240deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar10 { - -webkit-animation-delay: -0.2497s; - animation-delay: -0.2497s; - -webkit-transform: rotate( 270deg ) translate( 0, -130% ); - -moz-transform: rotate( 270deg ) translate( 0, -130% ); - -ms-transform: rotate( 270deg ) translate( 0, -130% ); - transform: rotate( 270deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar11 { - -webkit-animation-delay: -0.167s; - animation-delay: -0.167s; - -webkit-transform: rotate( 300deg ) translate( 0, -130% ); - -moz-transform: rotate( 300deg ) translate( 0, -130% ); - -ms-transform: rotate( 300deg ) translate( 0, -130% ); - transform: rotate( 300deg ) translate( 0, -130% ); -} - -.emotion-4 .InnerBar12 { - -webkit-animation-delay: -0.0833s; - animation-delay: -0.0833s; - -webkit-transform: rotate( 330deg ) translate( 0, -130% ); - -moz-transform: rotate( 330deg ) translate( 0, -130% ); - -ms-transform: rotate( 330deg ) translate( 0, -130% ); - transform: rotate( 330deg ) translate( 0, -130% ); -} - -
    -
    -