From 340f3a25d7f5746dade4cdd6a443021eacc06692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 18 Nov 2022 04:40:48 -0700 Subject: [PATCH 001/384] Fix `upgrader_process_complete` hook for `wp_theme_has_theme_json` (#45881) --- lib/compat/wordpress-6.2/default-filters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index 860cf9a8bf412a..861b8a01421c62 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -19,4 +19,4 @@ add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); -add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' ); +add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme', 10, 2 ); From 12abef834fe0b1af8016e87615229fa5f04f5392 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Fri, 18 Nov 2022 15:21:55 +0100 Subject: [PATCH 002/384] [Mobile] Update React Native Android E2E runner (#45840) * Update React Native Android E2E runner config * Remove unused target matrix value --- .github/workflows/rnmobile-android-runner.yml | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index fec513d88e6355..093c2a99ad4a07 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -14,11 +14,12 @@ concurrency: jobs: test: - runs-on: macos-latest + runs-on: macos-12 if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: native-test-name: [gutenberg-editor-initial-html] + api-level: [29] steps: - name: checkout @@ -29,7 +30,6 @@ jobs: with: distribution: 'temurin' java-version: '11' - cache: 'gradle' - name: Use desired version of NodeJS uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1 @@ -39,17 +39,39 @@ jobs: - run: npm ci - - name: Restore Gradle cache + - name: Gradle cache + uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # v2.3.3 + + - name: AVD cache uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + arch: x86_64 + profile: Nexus 6 + script: echo "Generated AVD snapshot for caching." - - uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 + - name: Run tests + uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 with: - api-level: 28 - emulator-build: 7425822 # https://github.com/ReactiveCircus/android-emulator-runner/issues/160#issuecomment-868615730 - profile: pixel_xl + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + arch: x86_64 + profile: Nexus 6 script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 From 23c47b7f4f02a90dd3577b0dd86c165e04dfe1c8 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Fri, 18 Nov 2022 16:49:45 +0200 Subject: [PATCH 003/384] [Inserter]: Replace text in `Reusable` tab with an icon (#45851) * [Inserter]: Replace text in `Reusable` tab with an icon * update styles and properly pass the icon * Polish * Use padding. * Update padding Co-authored-by: jasmussen --- packages/block-editor/src/components/inserter/style.scss | 6 ++++++ packages/block-editor/src/components/inserter/tabs.js | 2 ++ packages/components/src/search-control/style.scss | 4 ++++ packages/e2e-test-utils/src/inserter.js | 4 ++-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 22588e5c3a7b48..ad5a2d6642b4a2 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -125,6 +125,12 @@ $block-inserter-tabs-height: 44px; .components-tab-panel__tabs-item { flex-grow: 1; margin-bottom: -$border-width; + &[id$="reusable"] { + flex-grow: inherit; + // These are to align the `reusable` icon with the search icon. + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; + } } } diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index afc1c74e0a280d..24bfade8b2f4be 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { symbol as reusableBlockIcon } from '@wordpress/icons'; import { useMemo } from '@wordpress/element'; import { TabPanel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -19,6 +20,7 @@ const reusableBlocksTab = { name: 'reusable', /* translators: Reusable blocks tab title in the block inserter. */ title: __( 'Reusable' ), + icon: reusableBlockIcon, }; function InserterTabs( { diff --git a/packages/components/src/search-control/style.scss b/packages/components/src/search-control/style.scss index ddd9d0e5b06e3b..c02250de4f52ee 100644 --- a/packages/components/src/search-control/style.scss +++ b/packages/components/src/search-control/style.scss @@ -10,6 +10,10 @@ width: 100%; height: $grid-unit-60; + // Unset inherited values. + margin-left: 0; + margin-right: 0; + /* Fonts smaller than 16px causes mobile safari to zoom. */ font-size: $mobile-text-min-font-size; @include break-small { diff --git a/packages/e2e-test-utils/src/inserter.js b/packages/e2e-test-utils/src/inserter.js index ba11908fc4fa39..58e86cd0cf8f7e 100644 --- a/packages/e2e-test-utils/src/inserter.js +++ b/packages/e2e-test-utils/src/inserter.js @@ -142,12 +142,12 @@ export async function searchForReusableBlock( searchTerm ) { // fetched. They aren't fetched until an inserter is used or the post // already contains reusable blocks, so wait for the tab to appear. await page.waitForXPath( - '//div[contains(@class, "block-editor-inserter__tabs")]//button[text()="Reusable"]' + '//div[contains(@class, "block-editor-inserter__tabs")]//button[@aria-label="Reusable"]' ); // Select the reusable blocks tab. const tab = await page.waitForXPath( - '//div[contains(@class, "block-editor-inserter__tabs")]//button[text()="Reusable"]' + '//div[contains(@class, "block-editor-inserter__tabs")]//button[@aria-label="Reusable"]' ); await tab.click(); await page.waitForSelector( INSERTER_SEARCH_SELECTOR ); From 13c529a870bc483fa156bcf6ac6ec9878bd43122 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:24:48 +0200 Subject: [PATCH 004/384] Components: Fix no-node-access in Theme tests (#45896) --- packages/components/src/theme/test/index.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/components/src/theme/test/index.tsx b/packages/components/src/theme/test/index.tsx index 5c46d6e4964245..a3e3137f0d5b7e 100644 --- a/packages/components/src/theme/test/index.tsx +++ b/packages/components/src/theme/test/index.tsx @@ -28,21 +28,13 @@ describe( 'Theme', () => { describe( 'accent color', () => { it( 'it does not define the accent color (and its variations) as a CSS variable when the `accent` prop is undefined', () => { render( - + Inner ); - const inner = screen.getByText( 'Inner' ); - - if ( inner?.parentElement === null ) { - throw new Error( - 'Somehow the `Theme` component does not render a DOM element?' - ); - } - const innerElementStyles = window.getComputedStyle( - inner?.parentElement + screen.getByTestId( 'theme' ) ); expect( @@ -66,14 +58,12 @@ describe( 'Theme', () => { it( 'it defines the accent color (and its variations) as a CSS variable', () => { render( - + Inner ); - const inner = screen.getByText( 'Inner' ); - - expect( inner?.parentElement ).toHaveStyle( { + expect( screen.getByTestId( 'theme' ) ).toHaveStyle( { '--wp-components-color-accent': '#123abc', '--wp-components-color-accent-darker-10': '#0e2c8d', '--wp-components-color-accent-darker-20': '#091d5f', From ab16f64beee5ccb8d2f6785620013957dce8e629 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:25:06 +0200 Subject: [PATCH 005/384] Components: Fix no-node-access in Text tests (#45898) --- .../text/test/__snapshots__/index.tsx.snap | 1 + packages/components/src/text/test/index.tsx | 144 ++++++++++++------ 2 files changed, 99 insertions(+), 46 deletions(-) diff --git a/packages/components/src/text/test/__snapshots__/index.tsx.snap b/packages/components/src/text/test/__snapshots__/index.tsx.snap index e7fa80f09625cf..f63684c25d8695 100644 --- a/packages/components/src/text/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/text/test/__snapshots__/index.tsx.snap @@ -36,6 +36,7 @@ exports[`Text should render highlighted words with highlightCaseSensitive 1`] = class="components-truncate components-text emotion-0 emotion-1" data-wp-c16t="true" data-wp-component="Text" + role="heading" > { } ); test( 'should render optimizeReadabilityFor', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { color: COLORS.white, } ); } ); test( 'should render truncate', () => { - const { container } = render( Lorem ipsum. ); - expect( container.firstChild ).toHaveStyle( { + render( + + Lorem ipsum. + + ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { textOverflow: 'ellipsis', } ); } ); test( 'should render size', () => { - const { container } = render( Lorem ipsum. ); - expect( container.firstChild ).toHaveStyle( { + render( + + Lorem ipsum. + + ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { fontSize: getFontSize( 'title' ), } ); } ); test( 'should render custom size', () => { - const { container } = render( Lorem ipsum. ); - expect( container.firstChild ).toHaveStyle( { + render( + + Lorem ipsum. + + ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { fontSize: getFontSize( 15 ), } ); } ); test( 'should render variant', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { color: COLORS.gray[ 700 ], } ); } ); test( 'should render as another element', () => { - const { container } = render( Lorem ipsum. ); - expect( container.firstChild?.nodeName ).toBe( 'DIV' ); + render( + + Lorem ipsum. + + ); + expect( screen.getByRole( 'heading' )?.nodeName ).toBe( 'DIV' ); } ); test( 'should render align', () => { - const { container: centerAlignedContainer } = render( - Lorem ipsum. - ); - const { container: defaultAlignedContainer } = render( - Lorem ipsum. + render( + <> + + Lorem ipsum. + + Lorem ipsum. + ); - expect( - defaultAlignedContainer.children[ 0 ] - ).toMatchStyleDiffSnapshot( centerAlignedContainer.children[ 0 ] ); + expect( screen.getByRole( 'note' ) ).toMatchStyleDiffSnapshot( + screen.getByRole( 'heading' ) + ); } ); test( 'should render color', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild ).toHaveStyle( { color: 'orange' } ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { + color: 'orange', + } ); } ); test( 'should render display', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { display: 'inline-flex', } ); } ); test( 'should render highlighted words', async () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild?.childNodes ).toHaveLength( 5 ); + expect( screen.getByRole( 'heading' )?.childNodes ).toHaveLength( 5 ); const words = await screen.findAllByText( 'm' ); expect( words ).toHaveLength( 2 ); words.forEach( ( word ) => expect( word.tagName ).toEqual( 'MARK' ) ); } ); test( 'should render highlighted words with undefined passed', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); // It'll have a length of 1 because there shouldn't be anything but the single span being rendered. - expect( container.firstChild?.childNodes ).toHaveLength( 1 ); + expect( screen.getByRole( 'heading' )?.childNodes ).toHaveLength( 1 ); } ); test( 'should render highlighted words with highlightCaseSensitive', () => { const { container } = render( - + Lorem ipsum. ); expect( container ).toMatchSnapshot(); // It'll have a length of 1 because there shouldn't be anything but the single span being rendered. - expect( container.firstChild?.childNodes ).toHaveLength( 1 ); + expect( screen.getByRole( 'heading' )?.childNodes ).toHaveLength( 1 ); } ); test( 'should render isBlock', () => { - const { container } = render( Lorem ipsum. ); - expect( container.firstChild ).toHaveStyle( { + render( + + Lorem ipsum. + + ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { display: 'block', } ); } ); test( 'should render lineHeight', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild ).toHaveStyle( { lineHeight: '1.5' } ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { + lineHeight: '1.5', + } ); } ); test( 'should render upperCase', () => { - const { container } = render( Lorem ipsum. ); - expect( container.firstChild ).toHaveStyle( { + render( + + Lorem ipsum. + + ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { textTransform: 'uppercase', } ); } ); test( 'should render weight', () => { - const { container } = render( - Lorem ipsum. + render( + + Lorem ipsum. + ); - expect( container.firstChild ).toHaveStyle( { fontWeight: '700' } ); + expect( screen.getByRole( 'heading' ) ).toHaveStyle( { + fontWeight: '700', + } ); } ); } ); From 8e3d38af1d96fdc44f903091c3a25cbb0b5fabf8 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Fri, 18 Nov 2022 16:57:25 +0000 Subject: [PATCH 006/384] Update Changelog for 14.5.4 --- changelog.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/changelog.txt b/changelog.txt index 809e3f5ebc6bc4..3854aa54dfeb94 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 14.5.4 = + +## Changelog + +### Fixes + +#### Global Styles +- Fix the `upgrader_process_complete` hook for `wp_theme_has_theme_json`. ([45881](https://github.com/WordPress/gutenberg/pull/45881)) + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal + + + = 14.5.3 = From aa422e2942f8b0517c93306943d2b80f77dc900b Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Fri, 18 Nov 2022 21:55:13 +0200 Subject: [PATCH 007/384] Disable distraction free for small viewports (#45591) --- packages/edit-post/src/components/header/index.js | 6 +++--- packages/edit-post/src/components/layout/index.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index ed4a3ca94609e6..fb98f05970bb09 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -25,6 +25,7 @@ import { store as editPostStore } from '../../store'; import TemplateTitle from './template-title'; function Header( { setEntitiesSavedStatesCallback } ) { + const isLargeViewport = useViewportMatch( 'large' ); const { hasActiveMetaboxes, isPublishSidebarOpened, @@ -40,13 +41,12 @@ function Header( { setEntitiesSavedStatesCallback } ) { showIconLabels: select( editPostStore ).isFeatureActive( 'showIconLabels' ), isDistractionFree: - select( editPostStore ).isFeatureActive( 'distractionFree' ), + select( editPostStore ).isFeatureActive( 'distractionFree' ) && + isLargeViewport, } ), [] ); - const isLargeViewport = useViewportMatch( 'large' ); - const classes = classnames( 'edit-post-header' ); const slideY = { diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 7ab7be65e54f26..c4eaf068da5a56 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -66,6 +66,7 @@ const interfaceLabels = { function Layout( { styles } ) { const isMobileViewport = useViewportMatch( 'medium', '<' ); const isHugeViewport = useViewportMatch( 'huge', '>=' ); + const isLargeViewport = useViewportMatch( 'large' ); const { openGeneralSidebar, closeGeneralSidebar, setIsInserterOpened } = useDispatch( editPostStore ); const { createErrorNotice } = useDispatch( noticesStore ); @@ -116,7 +117,8 @@ function Layout( { styles } ) { showIconLabels: select( editPostStore ).isFeatureActive( 'showIconLabels' ), isDistractionFree: - select( editPostStore ).isFeatureActive( 'distractionFree' ), + select( editPostStore ).isFeatureActive( 'distractionFree' ) && + isLargeViewport, showBlockBreadcrumbs: select( editPostStore ).isFeatureActive( 'showBlockBreadcrumbs' ), From 225a22ae18aee44ff2039fcf6826838864d15dd5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:33:11 +0200 Subject: [PATCH 008/384] Components: Improve `Dropdown` tests (#45911) * Get rid of expectPopoverVisible * Get rid of expectButtonExpanded() * Get rid of getButtonElement() * fireEvent -> userEvent * Wait for the popover to be displayed * Fix maybe intentional typos * Get rid of getOpenCloseButton() * Improve comment * Remove redundant variables --- .../components/src/dropdown/test/index.js | 88 ++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/packages/components/src/dropdown/test/index.js b/packages/components/src/dropdown/test/index.js index 96947aca5b1282..b4d4f81fde8d1a 100644 --- a/packages/components/src/dropdown/test/index.js +++ b/packages/components/src/dropdown/test/index.js @@ -1,68 +1,57 @@ /** * External dependencies */ -import { fireEvent, render } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ import Dropdown from '../'; -function getButtonElement( container ) { - return container.querySelector( 'button' ); -} -function getOpenCloseButton( container, selector ) { - return container.querySelector( selector ); -} - describe( 'Dropdown', () => { - function expectPopoverVisible( container, value ) { - const popover = container.querySelector( '.components-popover' ); - if ( value ) { - expect( popover ).toBeTruthy(); - } else { - expect( popover ).toBeFalsy(); - } - } - - it( 'should toggle the dropdown properly', () => { - const expectButtonExpanded = ( container, expanded ) => { - expect( container.querySelectorAll( 'button' ) ).toHaveLength( 1 ); - expect( getButtonElement( container ) ).toHaveAttribute( - 'aria-expanded', - expanded.toString() - ); - }; - - const { - container: { firstChild: dropdownContainer }, - } = render( + it( 'should toggle the dropdown properly', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const { unmount } = render( ( ) } renderContent={ () => test } + popoverProps={ { 'data-testid': 'popover' } } /> ); - expectButtonExpanded( dropdownContainer, false ); - expectPopoverVisible( dropdownContainer, false ); + const button = screen.getByRole( 'button', { expanded: false } ); + + expect( button ).toBeVisible(); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); - const button = getButtonElement( dropdownContainer ); - fireEvent.click( button ); + await user.click( button ); - expectButtonExpanded( dropdownContainer, true ); - expectPopoverVisible( dropdownContainer, true ); + expect( + screen.getByRole( 'button', { expanded: true } ) + ).toBeVisible(); + + await waitFor( () => + expect( screen.getByTestId( 'popover' ) ).toBeVisible() + ); + + // Cleanup remaining effects, like the delayed popover positioning + unmount(); } ); - it( 'should close the dropdown when calling onClose', () => { - const { - container: { firstChild: dropdownContainer }, - } = render( + it( 'should close the dropdown when calling onClose', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + render( { aria-expanded={ isOpen } onClick={ onToggle } > - Toggleee + Toggle , , ] } renderContent={ () => null } + popoverProps={ { 'data-testid': 'popover' } } /> ); - expectPopoverVisible( dropdownContainer, false ); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); - const openButton = getOpenCloseButton( dropdownContainer, '.open' ); - fireEvent.click( openButton ); + await user.click( screen.getByRole( 'button', { name: 'Toggle' } ) ); - expectPopoverVisible( dropdownContainer, true ); + await waitFor( () => + expect( screen.getByTestId( 'popover' ) ).toBeVisible() + ); - const closeButton = getOpenCloseButton( dropdownContainer, '.close' ); - fireEvent.click( closeButton ); + await user.click( screen.getByRole( 'button', { name: 'close' } ) ); - expectPopoverVisible( dropdownContainer, false ); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); } ); } ); From b862a97ba27b1b3068330c62938d3f6496f4471c Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:38:39 +0200 Subject: [PATCH 009/384] Element: Fix no-node-access in createInterpolateElement (#45894) --- .../element/src/test/create-interpolate-element.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/element/src/test/create-interpolate-element.js b/packages/element/src/test/create-interpolate-element.js index b8c5d4757e1cb7..e78d3b1c558441 100644 --- a/packages/element/src/test/create-interpolate-element.js +++ b/packages/element/src/test/create-interpolate-element.js @@ -209,15 +209,13 @@ describe( 'createInterpolateElement', () => { }; const { container, rerender } = render( ); - expect( container.firstChild ).toContainHTML( 'string!' ); - expect( container.firstChild ).not.toContainHTML( '' ); + expect( container ).toContainHTML( 'string!' ); + expect( container ).not.toContainHTML( '' ); rerender( ); - expect( container.firstChild ).toContainHTML( - 'string!' - ); - expect( container.firstChild ).not.toContainHTML( '' ); + expect( container ).toContainHTML( 'string!' ); + expect( container ).not.toContainHTML( '' ); } ); it( 'handles parsing emojii correctly', () => { const testString = '👳‍♀️🚨🤷‍♂️⛈️fully here'; From 109aec494a01414b5abc83b1999bd610b8ae21c7 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:38:59 +0200 Subject: [PATCH 010/384] Components: Fix no-node-access in Sandbox tests (#45908) --- packages/components/src/sandbox/test/index.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/components/src/sandbox/test/index.js b/packages/components/src/sandbox/test/index.js index d91dce1842199a..f25bdca675cbfd 100644 --- a/packages/components/src/sandbox/test/index.js +++ b/packages/components/src/sandbox/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; /** * WordPress dependencies @@ -16,15 +16,15 @@ import Sandbox from '../'; describe( 'Sandbox', () => { const TestWrapper = () => { const [ html, setHtml ] = useState( - // MuatationObserver implementation from JSDom does not work as intended + // MutationObserver implementation from JSDom does not work as intended // with iframes so we need to ignore it for the time being. '' + - '' + '' ); const updateHtml = () => { setHtml( - '' + '' ); }; @@ -33,18 +33,19 @@ describe( 'Sandbox', () => { - + ); }; it( 'should rerender with new emdeded content if html prop changes', () => { - const { container } = render( ); + render( ); - const iframe = container.querySelector( '.components-sandbox' ); + const iframe = screen.getByTitle( 'Sandbox Title' ); - let sandboxedIframe = - iframe.contentWindow.document.body.querySelector( '.mock-iframe' ); + let sandboxedIframe = within( + iframe.contentWindow.document.body + ).getByTitle( 'Mock Iframe' ); expect( sandboxedIframe ).toHaveAttribute( 'src', @@ -53,8 +54,9 @@ describe( 'Sandbox', () => { fireEvent.click( screen.getByRole( 'button' ) ); - sandboxedIframe = - iframe.contentWindow.document.body.querySelector( '.mock-iframe' ); + sandboxedIframe = within( + iframe.contentWindow.document.body + ).getByTitle( 'Mock Iframe' ); expect( sandboxedIframe ).toHaveAttribute( 'src', From 8795c6e336d7cdf69caa02762bb3a0f4681c6fa7 Mon Sep 17 00:00:00 2001 From: Marissa <85708316+marissa-makes@users.noreply.github.com> Date: Sat, 19 Nov 2022 00:01:33 -0500 Subject: [PATCH 011/384] BlockVariationPicker: Remove unnecessary aria role (#45916) --- .../src/components/block-variation-picker/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-variation-picker/index.js b/packages/block-editor/src/components/block-variation-picker/index.js index 24bdfa272c34b2..27e214ec39bb07 100644 --- a/packages/block-editor/src/components/block-variation-picker/index.js +++ b/packages/block-editor/src/components/block-variation-picker/index.js @@ -49,10 +49,7 @@ function BlockVariationPicker( { className="block-editor-block-variation-picker__variation" label={ variation.description || variation.title } /> - + { variation.title } From 9d936e44729d65d1522f1f2eccffd587c205f420 Mon Sep 17 00:00:00 2001 From: Seth Miller Date: Sun, 20 Nov 2022 19:19:35 -0500 Subject: [PATCH 012/384] Added InspectorControls import to example (#45872) * Added InspectorControls import to example Fixes InspectorControls is not defined * Update docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md Co-authored-by: Ryan Welcher --- .../block-tutorial/extending-the-query-loop-block.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md index 3956971645f86a..e43dcb9727088d 100644 --- a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md +++ b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md @@ -192,6 +192,8 @@ Notice that we have also disabled the `postType` control. When the user selects Because our plugin uses custom attributes that we need to query, we want to add our own controls to allow the users to select those instead of the ones we have just disabled from the core inspector controls. We can do this via a [React HOC](https://reactjs.org/docs/higher-order-components.html) hooked into a [block filter](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/), like so: ```jsx +import { InspectorControls } from '@wordpress/block-editor'; + export const withBookQueryControls = ( BlockEdit ) => ( props ) => { // We only want to add these controls if it is our variation, // so here we can implement a custom logic to check for that, similiar From 5046795e516f6cb02b9382c3354ce48dea3bda73 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sat, 19 Nov 2022 18:52:51 -0700 Subject: [PATCH 013/384] Perf Tests: Fix test build process to call wp-scripts In #45284 we started skipping the generate-types phase of the build when running the performance tests. This was done to avoid the time it takes to generate those types when they are ignored by the rest of the test suite. In that patch we accidentally skipped running `wp-scripts build`, and in this patch we're bringing that back to fix the mistake. --- bin/plugin/commands/performance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index a7f9c81e8cebb3..31a537f373731d 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -276,7 +276,7 @@ async function runPerformanceTests( branches, options ) { log( ` >> Building the ${ fancyBranch } branch` ); await runShellScript( - 'npm ci && node ./bin/packages/build.js', + 'npm ci && npm run prebuild:packages && node ./bin/packages/build.js && npx wp-scripts build', buildPath ); } From 35f25ab846f30eccbbf48ea6911e88f35f030c5b Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Sun, 20 Nov 2022 22:00:48 -0700 Subject: [PATCH 014/384] Add submenu menu item to list view (#45794) --- .../src/components/off-canvas-editor/block.js | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/off-canvas-editor/block.js b/packages/block-editor/src/components/off-canvas-editor/block.js index 50119ebddfb579..a0bf8529a4d01c 100644 --- a/packages/block-editor/src/components/off-canvas-editor/block.js +++ b/packages/block-editor/src/components/off-canvas-editor/block.js @@ -6,10 +6,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { hasBlockSupport } from '@wordpress/blocks'; +import { createBlock, hasBlockSupport } from '@wordpress/blocks'; import { __experimentalTreeGridCell as TreeGridCell, __experimentalTreeGridItem as TreeGridItem, + MenuItem, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { moreVertical } from '@wordpress/icons'; @@ -59,7 +60,7 @@ function ListViewBlock( { } ) { const cellRef = useRef( null ); const [ isHovered, setIsHovered ] = useState( false ); - const { clientId } = block; + const { clientId, attributes } = block; const { isLocked, isContentLocked } = useBlockLock( clientId ); const forceSelectionContentLock = useSelect( @@ -86,7 +87,8 @@ function ListViewBlock( { ( isSelected && selectedClientIds[ selectedClientIds.length - 1 ] === clientId ); - const { toggleBlockHighlight } = useDispatch( blockEditorStore ); + const { replaceBlock, toggleBlockHighlight } = + useDispatch( blockEditorStore ); const blockInformation = useBlockDisplayInformation( clientId ); const blockName = useSelect( @@ -355,7 +357,34 @@ function ListViewBlock( { } } disableOpenOnArrowDown __experimentalSelectBlock={ updateSelection } - /> + > + { ( { onClose } ) => ( + { + const newLink = createBlock( + 'core/navigation-link' + ); + const newSubmenu = createBlock( + 'core/navigation-submenu', + attributes, + block.innerBlocks + ? [ + ...block.innerBlocks, + newLink, + ] + : [ newLink ] + ); + replaceBlock( + clientId, + newSubmenu + ); + onClose(); + } } + > + { __( 'Add a submenu item' ) } + + ) } + ) } From 1b69ba2b9b42fe1b6fee765d06c401dd1ac710c6 Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Sun, 20 Nov 2022 23:02:25 -0800 Subject: [PATCH 015/384] Storybook: Opt in to story store v7 (#42486) * Opt in to code-split stories * Revert glob * Move story utility files to subfolders Co-authored-by: Lena Morita --- .../src/components/inserter/stories/index.js | 2 +- .../inserter/stories/{ => utils}/fixtures.js | 0 .../src/navigation/stories/index.js | 12 ++++----- .../stories/{ => utils}/controlled-state.js | 6 ++--- .../navigation/stories/{ => utils}/default.js | 6 ++--- .../navigation/stories/{ => utils}/group.js | 8 +++--- .../stories/{ => utils}/hide-if-empty.js | 6 ++--- .../stories/{ => utils}/more-examples.js | 8 +++--- .../navigation/stories/{ => utils}/search.js | 10 ++++---- .../src/tools-panel/stories/index.js | 2 +- .../tools-panel-with-item-group-slot.js | 25 +++++++++++-------- packages/components/tsconfig.json | 4 +-- storybook/main.js | 2 ++ 13 files changed, 48 insertions(+), 43 deletions(-) rename packages/block-editor/src/components/inserter/stories/{ => utils}/fixtures.js (100%) rename packages/components/src/navigation/stories/{ => utils}/controlled-state.js (96%) rename packages/components/src/navigation/stories/{ => utils}/default.js (93%) rename packages/components/src/navigation/stories/{ => utils}/group.js (86%) rename packages/components/src/navigation/stories/{ => utils}/hide-if-empty.js (90%) rename packages/components/src/navigation/stories/{ => utils}/more-examples.js (96%) rename packages/components/src/navigation/stories/{ => utils}/search.js (89%) rename packages/components/src/tools-panel/stories/{ => utils}/tools-panel-with-item-group-slot.js (92%) diff --git a/packages/block-editor/src/components/inserter/stories/index.js b/packages/block-editor/src/components/inserter/stories/index.js index c74ca5fe79ee1b..f6949653c87872 100644 --- a/packages/block-editor/src/components/inserter/stories/index.js +++ b/packages/block-editor/src/components/inserter/stories/index.js @@ -3,7 +3,7 @@ */ import BlockLibrary from '../library'; import BlockEditorProvider from '../../provider'; -import { patternCategories, patterns, reusableBlocks } from './fixtures'; +import { patternCategories, patterns, reusableBlocks } from './utils/fixtures'; import Inserter from '../'; export default { title: 'BlockEditor/Inserter' }; diff --git a/packages/block-editor/src/components/inserter/stories/fixtures.js b/packages/block-editor/src/components/inserter/stories/utils/fixtures.js similarity index 100% rename from packages/block-editor/src/components/inserter/stories/fixtures.js rename to packages/block-editor/src/components/inserter/stories/utils/fixtures.js diff --git a/packages/components/src/navigation/stories/index.js b/packages/components/src/navigation/stories/index.js index 0c28f8c58347d2..8fbdde44fe7dff 100644 --- a/packages/components/src/navigation/stories/index.js +++ b/packages/components/src/navigation/stories/index.js @@ -6,12 +6,12 @@ import NavigationBackButton from '../back-button'; import NavigationGroup from '../group'; import NavigationItem from '../item'; import NavigationMenu from '../menu'; -import { DefaultStory } from './default'; -import { GroupStory } from './group'; -import { ControlledStateStory } from './controlled-state'; -import { SearchStory } from './search'; -import { MoreExamplesStory } from './more-examples'; -import { HideIfEmptyStory } from './hide-if-empty'; +import { DefaultStory } from './utils/default'; +import { GroupStory } from './utils/group'; +import { ControlledStateStory } from './utils/controlled-state'; +import { SearchStory } from './utils/search'; +import { MoreExamplesStory } from './utils/more-examples'; +import { HideIfEmptyStory } from './utils/hide-if-empty'; import './style.css'; export default { diff --git a/packages/components/src/navigation/stories/controlled-state.js b/packages/components/src/navigation/stories/utils/controlled-state.js similarity index 96% rename from packages/components/src/navigation/stories/controlled-state.js rename to packages/components/src/navigation/stories/utils/controlled-state.js index 68c1be54f1bffb..ed7d652d0df778 100644 --- a/packages/components/src/navigation/stories/controlled-state.js +++ b/packages/components/src/navigation/stories/utils/controlled-state.js @@ -7,9 +7,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function ControlledStateStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/default.js b/packages/components/src/navigation/stories/utils/default.js similarity index 93% rename from packages/components/src/navigation/stories/default.js rename to packages/components/src/navigation/stories/utils/default.js index cbcfe53338811d..569cb8aa17b4bc 100644 --- a/packages/components/src/navigation/stories/default.js +++ b/packages/components/src/navigation/stories/utils/default.js @@ -6,9 +6,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function DefaultStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/group.js b/packages/components/src/navigation/stories/utils/group.js similarity index 86% rename from packages/components/src/navigation/stories/group.js rename to packages/components/src/navigation/stories/utils/group.js index 1c919d50464f23..7e11071a63b9a4 100644 --- a/packages/components/src/navigation/stories/group.js +++ b/packages/components/src/navigation/stories/utils/group.js @@ -6,10 +6,10 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; -import NavigationGroup from '../group'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; +import NavigationGroup from '../../group'; export function GroupStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/hide-if-empty.js b/packages/components/src/navigation/stories/utils/hide-if-empty.js similarity index 90% rename from packages/components/src/navigation/stories/hide-if-empty.js rename to packages/components/src/navigation/stories/utils/hide-if-empty.js index 0977a41ab18463..ef9e889932a895 100644 --- a/packages/components/src/navigation/stories/hide-if-empty.js +++ b/packages/components/src/navigation/stories/utils/hide-if-empty.js @@ -1,9 +1,9 @@ /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function HideIfEmptyStory() { return ( diff --git a/packages/components/src/navigation/stories/more-examples.js b/packages/components/src/navigation/stories/utils/more-examples.js similarity index 96% rename from packages/components/src/navigation/stories/more-examples.js rename to packages/components/src/navigation/stories/utils/more-examples.js index 2887ab1241c0c6..43bd33835db370 100644 --- a/packages/components/src/navigation/stories/more-examples.js +++ b/packages/components/src/navigation/stories/utils/more-examples.js @@ -7,10 +7,10 @@ import { Icon, wordpress, home } from '@wordpress/icons'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationGroup from '../group'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationGroup from '../../group'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function MoreExamplesStory() { const [ activeItem, setActiveItem ] = useState( 'child-1' ); diff --git a/packages/components/src/navigation/stories/search.js b/packages/components/src/navigation/stories/utils/search.js similarity index 89% rename from packages/components/src/navigation/stories/search.js rename to packages/components/src/navigation/stories/utils/search.js index d9bcd405cbc349..6fb1a176970233 100644 --- a/packages/components/src/navigation/stories/search.js +++ b/packages/components/src/navigation/stories/utils/search.js @@ -6,11 +6,11 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationGroup from '../group'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; -import { normalizedSearch } from '../utils'; +import Navigation from '../..'; +import NavigationGroup from '../../group'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; +import { normalizedSearch } from '../../utils'; const searchItems = [ { item: 'foo', title: 'Foo' }, diff --git a/packages/components/src/tools-panel/stories/index.js b/packages/components/src/tools-panel/stories/index.js index 9f03ebbede6372..49f9d366587cb8 100644 --- a/packages/components/src/tools-panel/stories/index.js +++ b/packages/components/src/tools-panel/stories/index.js @@ -494,7 +494,7 @@ export const WithConditionallyRenderedControl = () => { ); }; -export { ToolsPanelWithItemGroupSlot } from './tools-panel-with-item-group-slot'; +export { ToolsPanelWithItemGroupSlot } from './utils/tools-panel-with-item-group-slot'; const PanelWrapperView = styled.div` font-size: 13px; diff --git a/packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js similarity index 92% rename from packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js rename to packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js index 717fd3a4485275..d1895324df4c3b 100644 --- a/packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js +++ b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js @@ -12,17 +12,20 @@ import { useContext, useState } from '@wordpress/element'; /** * Internal dependencies */ -import Button from '../../button'; -import ColorIndicator from '../../color-indicator'; -import ColorPalette from '../../color-palette'; -import Dropdown from '../../dropdown'; -import Panel from '../../panel'; -import { FlexItem } from '../../flex'; -import { HStack } from '../../h-stack'; -import { Item, ItemGroup } from '../../item-group'; -import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '..'; -import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; -import { useCx } from '../../utils'; +import Button from '../../../button'; +import ColorIndicator from '../../../color-indicator'; +import ColorPalette from '../../../color-palette'; +import Dropdown from '../../../dropdown'; +import Panel from '../../../panel'; +import { FlexItem } from '../../../flex'; +import { HStack } from '../../../h-stack'; +import { Item, ItemGroup } from '../../../item-group'; +import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '../..'; +import { + createSlotFill, + Provider as SlotFillProvider, +} from '../../../slot-fill'; +import { useCx } from '../../../utils'; // Available border colors. const colors = [ diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 107f0ffe586417..04a8eaa8d4d9ec 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -37,8 +37,8 @@ "src/**/*.ios.js", "src/**/*.native.js", "src/**/react-native-*", - "src/**/stories/**.js", // only exclude js files, tsx files should be checked - "src/**/test/**.js", // only exclude js files, ts{x} files should be checked + "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked + "src/**/test/**/*.js", // only exclude js files, ts{x} files should be checked "src/index.js", "src/alignment-matrix-control", "src/angle-picker-control", diff --git a/storybook/main.js b/storybook/main.js index 717f42f7fb5e6d..29880d64557001 100644 --- a/storybook/main.js +++ b/storybook/main.js @@ -25,9 +25,11 @@ module.exports = { '@storybook/addon-actions', 'storybook-source-link', ], + framework: '@storybook/react', features: { babelModeV7: true, emotionAlias: false, + storyStoreV7: true, }, // Workaround: // https://github.com/storybookjs/storybook/issues/12270 From 5c445c240f2aeb5823fe6ea3a97c24aadf85aee3 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 21 Nov 2022 18:56:21 +1100 Subject: [PATCH 016/384] Style Engine: add first draft of contributing doc (#45930) * Initial commit. * Adding `@group` to style engine tests so they can be run altogether Finishing up first draft of CONTRIBUTING.md notes * Formattings * Apply suggestions from code review Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> --- packages/style-engine/CONTRIBUTING.md | 40 +++++++++++++++++++ packages/style-engine/README.md | 2 + ...-wp-style-engine-css-declarations-test.php | 1 + .../class-wp-style-engine-css-rule-test.php | 1 + ...s-wp-style-engine-css-rules-store-test.php | 1 + .../class-wp-style-engine-processor-test.php | 1 + phpunit/style-engine/style-engine-test.php | 2 + 7 files changed, 48 insertions(+) create mode 100644 packages/style-engine/CONTRIBUTING.md diff --git a/packages/style-engine/CONTRIBUTING.md b/packages/style-engine/CONTRIBUTING.md new file mode 100644 index 00000000000000..70bfddb8eb5011 --- /dev/null +++ b/packages/style-engine/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +This document contains information you might need to know when extending or debugging Style Engine code. + +## Workflow and build tooling + +The Style Engine PHP and Javascript (JS) files exist inside the `style-engine` package. + +In order to use the Style Engine in the Block Editor, these files must be compiled (in the case of JS) and copied to the build folder. + +When running the `npm run dev` script for example, webpack watches out for changes and will recompile/copy files as necessary if any changes are detected according to the rules in the [packages webpack config](https://github.com/WordPress/gutenberg/tree/HEAD/tools/webpack/packages.js). + +No other configuration is required for JS: webpack will compile and export the Style Engine code as it does with all dependencies listed in [package.json](https://github.com/WordPress/gutenberg/tree/HEAD/package.json). + +The PHP files for packages, however, have a couple of extra steps during the build process: + +1. Functions with the `wp_` prefix are replaced with `gutenberg_`. So, for example, `wp_some_function` becomes `gutenberg_some_function` in the build directory. The reason for this is so that the Block Editor can call Style Engine functions that may have evolved since, or have not yet been included in, any WordPress release. +2. For the same reasons, classes are given a `_Gutenberg` suffix: `WP_Style_Engine` becomes `WP_Style_Engine_Gutenberg`. The [packages webpack config](https://github.com/WordPress/gutenberg/tree/HEAD/tools/webpack/packages.js) contains a static list of PHP classes (`bundledPackagesPhpConfig`) that have to be copied and renamed during build. If you create a new PHP class in the Style Engine package, you should add your class name to the `replaceClasses` array. + +Remember: all PHP functions and methods inside the Style Engine package should use `wp_/WP_` prefixes. Usage outside of the package in Gutenberg can reference the `gutenberg` prefixes or suffixes from the built files. + +When updating existing PHP functions or methods, it's important to check the Block Editor codebase for calls to the equivalent `wp_` functions or classes as they may have to be updated to refer to `gutenberg_` or `_Gutenberg` in order for the updates to take effect. + +## Testing + +[JS unit tests](https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/src/test) are stored next to the source code in the `style-engine` package directory. + +To start the JS unit tests, run: + +`npm run test:unit packages/style-engine/src/test/` + +[PHP unit tests](https://github.com/WordPress/gutenberg/tree/HEAD/phpunit/style-engine) are located in the root `phpunit` directory. + +In order to test the latest version of the Style Engine and avoid conflicts with existing Core equivalents, all PHP unit tests call the `gutenberg_` functions and `_Gutenberg` classes. + +Therefore, Style Engine PHP source files should be parsed and copied to the build folder before running tests. During development, this will happen as part of the `npm run dev` script. You can also trigger a build by executing `npm run build`. + +To start the PHP unit tests, run: + +`npm run test:unit:php -- --group style-engine` diff --git a/packages/style-engine/README.md b/packages/style-engine/README.md index 317e48e0c37c48..999fab2aa835e9 100644 --- a/packages/style-engine/README.md +++ b/packages/style-engine/README.md @@ -19,6 +19,8 @@ Upcoming tasks on the roadmap include, but are not limited to, the following: For more information about the roadmap, please refer to [Block editor styles: initiatives and goals](https://make.wordpress.org/core/2022/06/24/block-editor-styles-initiatives-and-goals/) and the [Github project board](https://github.com/orgs/WordPress/projects/19). +If you're making changes or additions to the Style Engine, please take a moment to read the [notes on contributing](https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/CONTRIBUTING.md). + ## Backend API ### wp_style_engine_get_styles() diff --git a/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php b/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php index 5380cc81d58314..011de140d08927 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php @@ -9,6 +9,7 @@ /** * Tests registering, storing and generating CSS declarations. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Declarations_Gutenberg */ class WP_Style_Engine_CSS_Declarations_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php index 26ea41c7ce830f..02482c70e4d84a 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php @@ -9,6 +9,7 @@ /** * Tests for registering, storing and generating CSS rules. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Rule_Gutenberg */ class WP_Style_Engine_CSS_Rule_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php b/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php index 700f63c556c255..8529bff78e22c8 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php @@ -9,6 +9,7 @@ /** * Tests for registering, storing and retrieving a collection of CSS Rules (a store). * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Rules_Store_Gutenberg */ class WP_Style_Engine_CSS_Rules_Store_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-processor-test.php b/phpunit/style-engine/class-wp-style-engine-processor-test.php index cfbde08704bdcb..18392f5156fcc5 100644 --- a/phpunit/style-engine/class-wp-style-engine-processor-test.php +++ b/phpunit/style-engine/class-wp-style-engine-processor-test.php @@ -9,6 +9,7 @@ /** * Tests for compiling and rendering styles from a store of CSS rules. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_Processor_Gutenberg */ class WP_Style_Engine_Processor_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/style-engine-test.php b/phpunit/style-engine/style-engine-test.php index 5f4c57453e8a8f..66d8dd62865272 100644 --- a/phpunit/style-engine/style-engine-test.php +++ b/phpunit/style-engine/style-engine-test.php @@ -8,6 +8,8 @@ /** * Tests for registering, storing and generating styles. + * + * @group style-engine */ class WP_Style_Engine_Test extends WP_UnitTestCase { /** From 8f57b443358aa8b098589f20ee7d0109bd592db2 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 21 Nov 2022 13:13:29 +0400 Subject: [PATCH 017/384] Site Editor: Fix template list width (#45888) --- packages/edit-site/src/components/list/style.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/list/style.scss b/packages/edit-site/src/components/list/style.scss index e6cf7220cdf604..f18e90c00fc618 100644 --- a/packages/edit-site/src/components/list/style.scss +++ b/packages/edit-site/src/components/list/style.scss @@ -44,9 +44,12 @@ .interface-interface-skeleton__content { background: $white; - align-items: center; padding: $grid-unit-20; + .interface-navigable-region__stacker { + align-items: center; + } + @include break-medium() { padding: $grid-unit * 9; } From abc8d7e850e0e6162725fe22f1b1d033f3d22ee4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 21 Nov 2022 11:19:31 +0100 Subject: [PATCH 018/384] Cleanup the BlockPreview component (#45936) --- packages/base-styles/_z-index.scss | 1 - .../src/components/block-preview/README.md | 14 -------------- .../src/components/block-preview/index.js | 17 +++++------------ .../src/components/block-preview/live.js | 19 ------------------- .../src/components/block-preview/style.scss | 2 -- 5 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 packages/block-editor/src/components/block-preview/live.js diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 14305a93611977..9c7b55e78a69cc 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -7,7 +7,6 @@ $z-layers: ( ".block-editor-block-switcher__arrow": 1, ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 31, // When scrolled to top this toolbar needs to sit over block-editor-block-toolbar - ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__block-selection-button": 22, ".components-form-toggle__input": 1, ".edit-post-text-editor__toolbar": 1, diff --git a/packages/block-editor/src/components/block-preview/README.md b/packages/block-editor/src/components/block-preview/README.md index 6df2cb6773878b..0da7348817cd85 100644 --- a/packages/block-editor/src/components/block-preview/README.md +++ b/packages/block-editor/src/components/block-preview/README.md @@ -34,17 +34,3 @@ Width of the preview container in pixels. Controls at what size the blocks will - **Default** `undefined` Padding for the preview container body. - -### `__experimentalLive` - -- **Type** `Boolean` -- **Default:** `false` - -Enables displaying previews without an iframe container. - -### `__experimentalOnClick` - -- **Type** `Function` -- **Default:** `undefined` - -Use this callback in combination with `__experimentalLive`. The callback is attached to the preview container element. diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index b01411591f098d..535a67438cad8e 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -14,7 +14,6 @@ import { memo, useMemo } from '@wordpress/element'; * Internal dependencies */ import BlockEditorProvider from '../provider'; -import LiveBlockPreview from './live'; import AutoHeightBlockPreview from './auto'; import { store as blockEditorStore } from '../../store'; import { BlockListItems } from '../block-list'; @@ -23,8 +22,6 @@ export function BlockPreview( { blocks, __experimentalPadding = 0, viewportWidth = 1200, - __experimentalLive = false, - __experimentalOnClick, __experimentalMinHeight, } ) { const originalSettings = useSelect( @@ -44,15 +41,11 @@ export function BlockPreview( { } return ( - { __experimentalLive ? ( - - ) : ( - - ) } + ); } diff --git a/packages/block-editor/src/components/block-preview/live.js b/packages/block-editor/src/components/block-preview/live.js deleted file mode 100644 index 5015c778defe78..00000000000000 --- a/packages/block-editor/src/components/block-preview/live.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Internal dependencies - */ -import BlockList from '../block-list'; - -export default function LiveBlockPreview( { onClick } ) { - return ( -
-
- -
-
- ); -} diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index 9736d6f6784c46..54dad77698d940 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -29,8 +29,6 @@ min-height: auto; .block-editor-block-list__insertion-point, - .block-editor-block-drop-zone, - .reusable-block-indicator, .block-list-appender { display: none; } From 1906e3923ef138cfd5e6a3984981a425c3d00690 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 12:38:16 +0200 Subject: [PATCH 019/384] Block Editor: Fix block alignment tests for React 18 (#45937) --- .../src/components/alignment-control/test/index.js | 5 ++++- .../src/components/block-alignment-control/test/index.js | 5 ++++- .../block-vertical-alignment-control/test/index.js | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/alignment-control/test/index.js b/packages/block-editor/src/components/alignment-control/test/index.js index 14f0d9a04e033c..c792b3e9b7f82b 100644 --- a/packages/block-editor/src/components/alignment-control/test/index.js +++ b/packages/block-editor/src/components/alignment-control/test/index.js @@ -52,7 +52,7 @@ describe( 'AlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align text \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call on change with undefined when a control is already active', async () => { diff --git a/packages/block-editor/src/components/block-alignment-control/test/index.js b/packages/block-editor/src/components/block-alignment-control/test/index.js index fd9f0f782e25d2..abf2a0a7c448ae 100644 --- a/packages/block-editor/src/components/block-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-alignment-control/test/index.js @@ -47,7 +47,7 @@ describe( 'BlockAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call onChange with undefined, when the control is already active', async () => { diff --git a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js index 2e54ee90268549..f2645357a197c3 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js @@ -45,7 +45,7 @@ describe( 'BlockVerticalAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); it( 'should call onChange with undefined, when the control is already active', async () => { From bbc5489c991e2030f9da042683c1b172bfd85215 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:14:47 +0200 Subject: [PATCH 020/384] Block Editor: Wait for popover positioning in `MediaReplaceFlow` tests (#45863) * Block Editor: Wait for popover positioning in MediaReplaceFlow tests * Remove an extra backtick * Use Element.closest() to locate closest popover * Fix comment * Remove an excess assertion * Reorder assertions * Introduce a less-specific way to assert positioning * Use the right element when locating the popover * Clarify element.style usage * Wait for the menu to get visible --- .../media-replace-flow/test/index.js | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/media-replace-flow/test/index.js b/packages/block-editor/src/components/media-replace-flow/test/index.js index 8d6bc6ca433782..d73da6c77c5d55 100644 --- a/packages/block-editor/src/components/media-replace-flow/test/index.js +++ b/packages/block-editor/src/components/media-replace-flow/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; /** @@ -32,6 +32,34 @@ function TestWrapper() { ); } +/** + * Returns the first found popover element up the DOM tree. + * + * @param {HTMLElement} element Element to start with. + * @return {HTMLElement|null} Popover element, or `null` if not found. + */ +function getWrappingPopoverElement( element ) { + return element.closest( '.components-popover' ); +} + +/** + * Asserts that the specified popover has already been positioned. + * Necessary because it will be positioned a bit later after it's displayed. + * + * We're intentionally not using `.toHaveStyle()` because we want to be + * less specific and avoid specific values for better test flexibility. + * + * @async + * + * @param {HTMLElement} popover Popover element. + */ +async function popoverIsPositioned( popover ) { + /* eslint-disable jest-dom/prefer-to-have-style */ + await waitFor( () => expect( popover.style.top ).not.toBe( '' ) ); + await waitFor( () => expect( popover.style.left ).not.toBe( '' ) ); + /* eslint-enable jest-dom/prefer-to-have-style */ +} + describe( 'General media replace flow', () => { it( 'renders successfully', () => { render( ); @@ -57,11 +85,11 @@ describe( 'General media replace flow', () => { name: 'Replace', } ) ); - const uploadMenu = screen.getByRole( 'menu' ); - expect( uploadMenu ).toBeInTheDocument(); - expect( uploadMenu ).not.toBeVisible(); + await popoverIsPositioned( getWrappingPopoverElement( uploadMenu ) ); + + await waitFor( () => expect( uploadMenu ).toBeVisible() ); } ); it( 'displays media URL', async () => { @@ -78,11 +106,13 @@ describe( 'General media replace flow', () => { } ) ); - expect( - screen.getByRole( 'link', { - name: 'example.media (opens in a new tab)', - } ) - ).toHaveAttribute( 'href', 'https://example.media' ); + const link = screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ); + + await popoverIsPositioned( getWrappingPopoverElement( link ) ); + + expect( link ).toHaveAttribute( 'href', 'https://example.media' ); } ); it( 'edits media URL', async () => { @@ -99,6 +129,14 @@ describe( 'General media replace flow', () => { } ) ); + await popoverIsPositioned( + getWrappingPopoverElement( + screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ) + ) + ); + await user.click( screen.getByRole( 'button', { name: 'Edit', From b55bd2335d32de45e4a7de63eff19ee1070fbce5 Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Mon, 21 Nov 2022 12:31:32 +0100 Subject: [PATCH 021/384] Ignore cached `wp_theme_has_theme_json` when `WP_DEBUG` is enabled (#45882) --- .../wordpress-6.2/get-global-styles-and-settings.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index b2637969fd8ef6..be9781556868c0 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -20,13 +20,17 @@ function wp_theme_has_theme_json() { $theme_has_support = wp_cache_get( $cache_key, $cache_group ); /** - * $theme_has_support is stored as a int in the cache. + * $theme_has_support is stored as an int in the cache. * * The reason not to store it as a boolean is to avoid working * with the $found parameter which apparently had some issues in some implementations * https://developer.wordpress.org/reference/functions/wp_cache_get/ */ - if ( 0 === $theme_has_support || 1 === $theme_has_support ) { + if ( + // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( 0 === $theme_has_support || 1 === $theme_has_support ) + ) { return (bool) $theme_has_support; } From 7ef776b97e760b165a34493412b260fa8a23fd41 Mon Sep 17 00:00:00 2001 From: Vijayan Date: Mon, 21 Nov 2022 18:06:17 +0530 Subject: [PATCH 022/384] Fix link & code markdown (#45708) - Fix Modal component link - Fix code markdown at `Unsupported: Multiple instances` --- packages/components/src/confirm-dialog/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/confirm-dialog/README.md b/packages/components/src/confirm-dialog/README.md index 2f6a764f7f8415..5ed36534898490 100644 --- a/packages/components/src/confirm-dialog/README.md +++ b/packages/components/src/confirm-dialog/README.md @@ -4,7 +4,7 @@ This feature is still experimental. "Experimental" means this is an early implementation subject to drastic and breaking changes. -`ConfirmDialog` is built of top of [`Modal`](/packages/components/src/modal/README.md] and displays a confirmation dialog, with _confirm_ and _cancel_ buttons. +`ConfirmDialog` is built of top of [`Modal`](/packages/components/src/modal/README.md) and displays a confirmation dialog, with _confirm_ and _cancel_ buttons. The dialog is confirmed by clicking the _confirm_ button or by pressing the `Enter` key. It is cancelled (closed) by clicking the _cancel_ button, by pressing the `ESC` key, or by clicking outside the dialog focus (i.e, the overlay). @@ -72,7 +72,7 @@ function Example() { ### Unsupported: Multiple instances -Multiple `ConfirmDialog's is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented. +Multiple `ConfirmDialog`s is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented. ## Custom Types From ba9f76cf9b7958cb7d9647577309829cdec188ab Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Mon, 21 Nov 2022 12:58:47 +0000 Subject: [PATCH 023/384] Site Logo: Apply width to logo container in editor (#45821) * Apply width to logo container * Move width to placeholder --- packages/block-library/src/site-logo/edit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 89b9e3f6eba01e..8b85798baee712 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -512,6 +512,9 @@ export default function LogoEdit( { className={ placeholderClassName } preview={ logoImage } withIllustration={ true } + style={ { + width, + } } > { content } From 5122e650ab09604645011dd11cd2cfc83c00f70f Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:11:33 +0200 Subject: [PATCH 024/384] Components: Fix `no-node-access` in `Grid` tests (#45900) * Components: Fix no-node-access in Grid tests * role -> data-testid --- packages/components/src/grid/test/grid.tsx | 62 +++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/components/src/grid/test/grid.tsx b/packages/components/src/grid/test/grid.tsx index 791dfa4f52216c..150dac578c80ab 100644 --- a/packages/components/src/grid/test/grid.tsx +++ b/packages/components/src/grid/test/grid.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -12,14 +12,14 @@ import CONFIG from '../../utils/config-values'; describe( 'props', () => { test( 'should render correctly', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 2, 1fr )', gap: `calc( ${ CONFIG.gridBase } * 3 )`, @@ -27,15 +27,15 @@ describe( 'props', () => { } ); test( 'should render gap', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 3, 1fr )', gap: `calc( ${ CONFIG.gridBase } * 4 )`, @@ -43,60 +43,60 @@ describe( 'props', () => { } ); test( 'should render custom columns', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 7, 1fr )', } ); } ); test( 'should render custom rows', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateRows: 'repeat( 7, 1fr )', } ); } ); test( 'should render align', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { alignItems: 'flex-start', display: 'grid', } ); } ); test( 'should render alignment spaced', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', alignItems: 'center', justifyContent: 'space-between', @@ -104,60 +104,60 @@ describe( 'props', () => { } ); test( 'should render justify', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', justifyContent: 'flex-start', } ); } ); test( 'should render isInline', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'inline-grid', gridTemplateColumns: 'repeat( 3, 1fr )', } ); } ); test( 'should render custom templateColumns', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: '1fr auto 1fr', } ); } ); test( 'should render custom templateRows', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateRows: '1fr auto 1fr', } ); From 37fc2a1dbc1671dabe3a4771b5b030814b452aae Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 21 Nov 2022 16:21:13 +0200 Subject: [PATCH 025/384] Enable easier drag and drop for navigation building (#45906) * tweaks the way navigation link and navigation submenu work to enable drag and drop in list view * Adds deps list to effects for submenu and navigation link blocks. Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Maggie <3593343+MaggieCabrera@users.noreply.github.com> Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Maggie <3593343+MaggieCabrera@users.noreply.github.com> Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> --- .../block-library/src/navigation-link/edit.js | 25 +++++++++++++++++-- .../src/navigation-submenu/edit.js | 7 ++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ed0acced4efe88..30a2f9ebb4b520 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -32,6 +32,7 @@ import { useBlockProps, store as blockEditorStore, getColorClassName, + useInnerBlocksProps, } from '@wordpress/block-editor'; import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; import { @@ -527,7 +528,9 @@ export default function NavigationLinkEdit( { const newSubmenu = createBlock( 'core/navigation-submenu', attributes, - innerBlocks + innerBlocks.length > 0 + ? innerBlocks + : [ createBlock( 'core/navigation-link' ) ] ); replaceBlock( clientId, newSubmenu ); } @@ -540,11 +543,14 @@ export default function NavigationLinkEdit( { if ( ! url ) { setIsLinkOpen( true ); } + }, [ url ] ); + + useEffect( () => { // If block has inner blocks, transform to Submenu. if ( hasChildren ) { transformToSubmenu(); } - }, [] ); + }, [ hasChildren ] ); /** * The hook shouldn't be necessary but due to a focus loss happening @@ -675,6 +681,20 @@ export default function NavigationLinkEdit( { onKeyDown, } ); + const ALLOWED_BLOCKS = [ + 'core/navigation-link', + 'core/navigation-submenu', + ]; + const DEFAULT_BLOCK = { + name: 'core/navigation-link', + }; + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: ALLOWED_BLOCKS, + __experimentalDefaultBlock: DEFAULT_BLOCK, + __experimentalDirectInsert: true, + renderAppender: false, + } ); + if ( ! url || isInvalid || isDraft ) { blockProps.onClick = () => setIsLinkOpen( true ); } @@ -915,6 +935,7 @@ export default function NavigationLinkEdit( { ) } +
); diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index c1dd94a0c10343..9810c7ebab05c6 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -541,6 +541,13 @@ export default function NavigationSubmenuEdit( { replaceBlock( clientId, newLinkBlock ); } + useEffect( () => { + // If block is empty, transform to Navigation Link. + if ( ! hasChildren ) { + transformToLink(); + } + }, [ hasChildren ] ); + const canConvertToLink = ! selectedBlockHasChildren || onlyDescendantIsEmptyLink; From f02087f7f8878a566b63cc02493ee3b24c855e34 Mon Sep 17 00:00:00 2001 From: chad1008 <13856531+chad1008@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:03:14 -0500 Subject: [PATCH 026/384] Components: Remove CircleIndicatorWrapper `focus-visible` outline (#45758) --- packages/components/CHANGELOG.md | 3 +++ .../styles/angle-picker-control-styles.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2e47db6c778689..c57f3b01d58256 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -13,6 +13,9 @@ - `ColorPalette`, `BorderBox`, `BorderBoxControl`: polish and DRY prop types, add default values ([#45463](https://github.com/WordPress/gutenberg/pull/45463)). - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +### Internal +- `AnglePickerControl`: remove `:focus-visible' outline on `CircleOutlineWrapper` ([#45758](https://github.com/WordPress/gutenberg/pull/45758)) + ### Bug Fix - `FormTokenField`: Fix duplicate input in IME composition ([#45607](https://github.com/WordPress/gutenberg/pull/45607)). diff --git a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js index 1a7ed713d72f18..29b38850c055f4 100644 --- a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js +++ b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js @@ -42,6 +42,10 @@ export const CircleIndicatorWrapper = styled.div` position: relative; width: 100%; height: 100%; + + :focus-visible { + outline: none; + } `; export const CircleIndicator = styled.div` From 6fa02c7e364424a907838dde14815d771e6e483a Mon Sep 17 00:00:00 2001 From: hiyascout <113260898+hiyascout@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:58:43 -0800 Subject: [PATCH 027/384] Update applying-styles-with-stylesheets.md (#45925) Fixed two typos in opening paragraph. ('someway' and 'this guides walks') --- .../block-tutorial/applying-styles-with-stylesheets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md index 815adfdf80c26d..e668a6ac762388 100644 --- a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -2,7 +2,7 @@ ## Overview -A block typically inserts markup (HTML) into post content that you want to style in someway. This guides walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. +A block typically inserts markup (HTML) into post content that you want to style in some way. This guide walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. ## Before you start From 453866d96b2b1857fd8a2bc9340f3856336c07c8 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Tue, 22 Nov 2022 10:39:14 +0200 Subject: [PATCH 028/384] Add a starting page for page list block's hierarchy (#45861) * adds a rootPageID attribute and handling * adds UI to control rootPageID * Rename root to parent, generate fixtures Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Joen A. <1204802+jasmussen@users.noreply.github.com> * PHP lint * PHP Lint Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Joen A. <1204802+jasmussen@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- .../block-library/src/page-list/block.json | 7 +- packages/block-library/src/page-list/edit.js | 65 +++++++++++++++++-- .../block-library/src/page-list/index.php | 9 +++ .../fixtures/blocks/core__page-list.json | 4 +- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 65e3cb1f65c8fc..8650c9d7d32749 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -429,7 +429,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** +- **Attributes:** rootPageID ## Paragraph diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 3068a1fb8bc008..144cecb428396c 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -7,7 +7,12 @@ "description": "Display a list of all pages.", "keywords": [ "menu", "navigation" ], "textdomain": "default", - "attributes": {}, + "attributes": { + "rootPageID": { + "type": "integer", + "default": 0 + } + }, "usesContext": [ "textColor", "customTextColor", diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index 71cbfa437a1f9a..ac7ed6162cf975 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -7,11 +7,18 @@ import classnames from 'classnames'; * WordPress dependencies */ import { + InspectorControls, BlockControls, useBlockProps, getColorClassName, } from '@wordpress/block-editor'; -import { ToolbarButton, Spinner, Notice } from '@wordpress/components'; +import { + PanelBody, + ToolbarButton, + Spinner, + Notice, + ComboboxControl, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo, useState, memo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -27,7 +34,13 @@ import { ItemSubmenuIcon } from '../navigation-link/icons'; // Performance of Navigation Links is not good past this value. const MAX_PAGE_COUNT = 100; -export default function PageListEdit( { context, clientId } ) { +export default function PageListEdit( { + context, + clientId, + attributes, + setAttributes, +} ) { + const { parentPageID } = attributes; const { pagesByParentId, totalPages, hasResolvedPages } = usePageData(); const isNavigationChild = 'showSubmenuIcon' in context; @@ -86,6 +99,7 @@ export default function PageListEdit( { context, clientId } ) {
@@ -93,8 +107,35 @@ export default function PageListEdit( { context, clientId } ) { } }; + const useParentOptions = () => { + const [ pages ] = useGetPages(); + return pages?.reduce( ( accumulator, page ) => { + accumulator.push( { + value: page.id, + label: page.title.rendered, + } ); + return accumulator; + }, [] ); + }; + return ( <> + + + + setAttributes( { parentPageID: value ?? 0 } ) + } + help={ __( + 'Choose a page to show only its subpages.' + ) } + /> + + { allowConvertToLinks && ( @@ -129,7 +170,7 @@ function useFrontPageId() { }, [] ); } -function usePageData() { +function useGetPages() { const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', 'page', @@ -142,10 +183,21 @@ function usePageData() { } ); + return [ pages, hasResolvedPages ]; +} + +function usePageData( pageId = 0 ) { + const [ pages, hasResolvedPages ] = useGetPages(); + return useMemo( () => { // TODO: Once the REST API supports passing multiple values to // 'orderby', this can be removed. // https://core.trac.wordpress.org/ticket/39037 + + if ( pageId !== 0 ) { + return pages.find( ( page ) => page.id === pageId ); + } + const sortedPages = [ ...( pages ?? [] ) ].sort( ( a, b ) => { if ( a.menu_order === b.menu_order ) { return a.title.rendered.localeCompare( b.title.rendered ); @@ -167,7 +219,7 @@ function usePageData() { hasResolvedPages, totalPages: pages?.length ?? null, }; - }, [ pages, hasResolvedPages ] ); + }, [ pageId, pages, hasResolvedPages ] ); } const PageItems = memo( function PageItems( { @@ -176,7 +228,10 @@ const PageItems = memo( function PageItems( { parentId = 0, depth = 0, } ) { - const pages = pagesByParentId.get( parentId ); + const parentPage = usePageData( parentId ); + const pages = pagesByParentId.get( parentId ) + ? pagesByParentId.get( parentId ) + : [ parentPage ]; const frontPageId = useFrontPageId(); if ( ! pages?.length ) { diff --git a/packages/block-library/src/page-list/index.php b/packages/block-library/src/page-list/index.php index 32acdad45ed635..7944f6f3a84ad6 100644 --- a/packages/block-library/src/page-list/index.php +++ b/packages/block-library/src/page-list/index.php @@ -252,6 +252,8 @@ function render_block_core_page_list( $attributes, $content, $block ) { static $block_id = 0; ++$block_id; + $parent_page_id = $attributes['parentPageID']; + $all_pages = get_pages( array( 'sort_column' => 'menu_order,post_title', @@ -306,6 +308,13 @@ function render_block_core_page_list( $attributes, $content, $block ) { $nested_pages = block_core_page_list_nest_pages( $top_level_pages, $pages_with_children ); + if ( 0 !== $parent_page_id ) { + $nested_pages = block_core_page_list_nest_pages( + $pages_with_children[ $parent_page_id ], + $pages_with_children + ); + } + $is_navigation_child = array_key_exists( 'showSubmenuIcon', $block->context ); $open_submenus_on_click = array_key_exists( 'openSubmenusOnClick', $block->context ) ? $block->context['openSubmenusOnClick'] : false; diff --git a/test/integration/fixtures/blocks/core__page-list.json b/test/integration/fixtures/blocks/core__page-list.json index 0bb67d8e44efd7..52e22aa9059abd 100644 --- a/test/integration/fixtures/blocks/core__page-list.json +++ b/test/integration/fixtures/blocks/core__page-list.json @@ -2,7 +2,9 @@ { "name": "core/page-list", "isValid": true, - "attributes": {}, + "attributes": { + "rootPageID": 0 + }, "innerBlocks": [] } ] From 20cc4aa86e3120449bad0bbf39e5bfb792376da5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:27:39 +0200 Subject: [PATCH 029/384] Lodash: Simplify a few isEmpty calls (#45525) --- packages/block-library/src/audio/edit.native.js | 3 +-- packages/block-library/src/embed/embed-preview.native.js | 3 +-- packages/block-library/src/gallery/gallery.native.js | 3 +-- packages/block-library/src/gallery/v1/gallery.native.js | 3 +-- packages/block-library/src/video/edit.native.js | 3 +-- .../components/src/mobile/bottom-sheet/switch-cell.native.js | 4 ++-- packages/editor/src/components/post-title/index.native.js | 5 ++--- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index 30ee44ff3f9487..fbc39ba190d3e2 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -228,7 +227,7 @@ function AudioEdit( { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty Audio caption. */ __( 'Audio caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index bb8df663baf281..87abc38348d56a 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; import classnames from 'classnames/dedupe'; /** @@ -52,7 +51,7 @@ const EmbedPreview = ( { styles[ `embed-preview__sandbox--align-${ align }` ]; function accessibilityLabelCreator( caption ) { - return isEmpty( caption ) + return ! caption ? /* translators: accessibility text. Empty Embed caption. */ __( 'Embed caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index 16c504f6ccc19a..a5174d0215c424 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -100,7 +99,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' diff --git a/packages/block-library/src/gallery/v1/gallery.native.js b/packages/block-library/src/gallery/v1/gallery.native.js index 7908d17988a1a4..c1d13cb6313e06 100644 --- a/packages/block-library/src/gallery/v1/gallery.native.js +++ b/packages/block-library/src/gallery/v1/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -142,7 +141,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' : sprintf( diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index fb4937ce5da733..99225ba5d5c569 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View, TouchableWithoutFeedback, Text } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -367,7 +366,7 @@ class VideoEdit extends Component { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty video caption. */ __( 'Video caption. Empty' ) : sprintf( diff --git a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js index b49f03902ddb85..9dcaf794df750b 100644 --- a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { Switch } from 'react-native'; -import { isEmpty } from 'lodash'; + /** * WordPress dependencies */ @@ -20,7 +20,7 @@ export default function BottomSheetSwitchCell( props ) { }; const getAccessibilityLabel = () => { - if ( isEmpty( cellProps.help ) ) { + if ( ! cellProps.help ) { return value ? sprintf( /* translators: accessibility text. Switch setting ON state. %s: Switch title. */ diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 0d1d87c81e417e..4da9d8c308110b 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -80,7 +79,7 @@ class PostTitle extends Component { getTitle( title, postType ) { if ( 'page' === postType ) { - return isEmpty( title ) + return ! title ? /* translators: accessibility text. empty page title. */ __( 'Page title. Empty' ) : sprintf( @@ -90,7 +89,7 @@ class PostTitle extends Component { ); } - return isEmpty( title ) + return ! title ? /* translators: accessibility text. empty post title. */ __( 'Post title. Empty' ) : sprintf( From a7be92761e4707fdf9de6fd5337862d82af02133 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:30:08 +0200 Subject: [PATCH 030/384] Lodash: Refactor away from `_.pick()` in block library (#45940) * Lodash: Refactor away from _.pick() in block library * Add missing empty section check to updateSelectedCell() * Revert "Add missing empty section check to updateSelectedCell()" This reverts commit 9e9bea5766d801d82a8b48cd5c57a45630433721. * Improve precision of approach * Null-proofing --- packages/block-library/src/gallery/shared.js | 9 ++++-- .../block-library/src/gallery/v1/shared.js | 9 ++++-- packages/block-library/src/image/edit.js | 9 ++++-- packages/block-library/src/image/image.js | 18 +++++++----- packages/block-library/src/site-logo/edit.js | 13 +++++++-- packages/block-library/src/table/state.js | 29 ++++++++++++++----- 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 4765b4395994f7..f3142bbbaf9da6 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -13,7 +13,12 @@ export function defaultColumnsNumber( imageCount ) { } export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/gallery/v1/shared.js b/packages/block-library/src/gallery/v1/shared.js index 484020cb9d58cf..9a0957cc7a120d 100644 --- a/packages/block-library/src/gallery/v1/shared.js +++ b/packages/block-library/src/gallery/v1/shared.js @@ -1,10 +1,15 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index baba2cee24ec33..4e504896b47094 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, isEmpty, pick } from 'lodash'; +import { get, isEmpty } from 'lodash'; /** * WordPress dependencies @@ -58,7 +58,12 @@ import { } from './constants'; export const pickRelevantMediaFiles = ( image, size ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', size, 'url' ] ) || get( image, [ 'media_details', 'sizes', size, 'source_url' ] ) || diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 7245ce232c64b7..1e4881181701b4 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, filter, isEmpty, map, pick } from 'lodash'; +import { get, filter, isEmpty, map } from 'lodash'; /** * WordPress dependencies @@ -135,12 +135,16 @@ export default function Image( { } = select( blockEditorStore ); const rootClientId = getBlockRootClientId( clientId ); - const settings = pick( getSettings(), [ - 'imageEditing', - 'imageSizes', - 'maxWidth', - 'mediaUpload', - ] ); + const settings = Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ + 'imageEditing', + 'imageSizes', + 'maxWidth', + 'mediaUpload', + ].includes( key ) + ) + ); return { ...settings, diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 8b85798baee712..0d752a5dbae31f 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { pick } from 'lodash'; /** * WordPress dependencies @@ -87,7 +86,11 @@ const SiteLogo = ( { ); return { title: siteEntities?.name, - ...pick( getSettings(), [ 'imageEditing', 'maxWidth' ] ), + ...Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ 'imageEditing', 'maxWidth' ].includes( key ) + ) + ), }; }, [] ); @@ -121,7 +124,11 @@ const SiteLogo = ( { alt={ alt } onLoad={ ( event ) => { setNaturalSize( - pick( event.target, [ 'naturalWidth', 'naturalHeight' ] ) + Object.fromEntries( + Object.entries( event.target ).filter( ( [ key ] ) => + [ 'naturalWidth', 'naturalHeight' ].includes( key ) + ) + ) ); } } /> diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 68fee2d0bda9b5..a1aafc31459365 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, mapValues, pick } from 'lodash'; +import { get, mapValues } from 'lodash'; const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; @@ -78,7 +78,11 @@ export function updateSelectedCell( state, selection, updateCell ) { return state; } - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); const { sectionName: selectionSectionName, rowIndex: selectionRowIndex } = selection; @@ -174,9 +178,12 @@ export function insertRow( state, { sectionName, rowIndex, columnCount } ) { [ 'cells', index ], {} ); - const inheritedAttributes = pick( - firstCellInColumn, - INHERITED_COLUMN_ATTRIBUTES + + const inheritedAttributes = Object.fromEntries( + Object.entries( firstCellInColumn ).filter( + ( [ key ] ) => + INHERITED_COLUMN_ATTRIBUTES.includes( key ) + ) ); return { @@ -220,7 +227,11 @@ export function deleteRow( state, { sectionName, rowIndex } ) { * @return {Object} New table state. */ export function insertColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section, sectionName ) => { // Bail early if the table section is empty. @@ -259,7 +270,11 @@ export function insertColumn( state, { columnIndex } ) { * @return {Object} New table state. */ export function deleteColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section ) => { // Bail early if the table section is empty. From 4c21122dab619ece1678bdfb41b1fc08304028df Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 22 Nov 2022 20:53:24 +1100 Subject: [PATCH 031/384] Style engine: trim multiple selector strings (#45873) * Trim multiple selector string, that is a string of selectors separated by a comma, passed to the style engine. This is so we can correctly align prettified content, and remove spaces between multiple selectors with the same styles. * Only remove selector white spaces when $should_prettify is true * Updated test --- .../class-wp-style-engine-css-rule.php | 6 +++-- .../class-wp-style-engine-css-rule-test.php | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index e1605517573313..76ba12673aeda5 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -113,8 +113,10 @@ public function get_css( $should_prettify = false, $indent_count = 0 ) { $declarations_indent = $should_prettify ? $indent_count + 1 : 0; $suffix = $should_prettify ? "\n" : ''; $spacer = $should_prettify ? ' ' : ''; - $selector = $should_prettify ? str_replace( ',', ",\n", $this->get_selector() ) : $this->get_selector(); - $css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent ); + // Trims any multiple selectors strings. + $selector = $should_prettify ? implode( ',', array_map( 'trim', explode( ',', $this->get_selector() ) ) ) : $this->get_selector(); + $selector = $should_prettify ? str_replace( array( ',' ), ",\n", $selector ) : $selector; + $css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent ); if ( empty( $css_declarations ) ) { return ''; diff --git a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php index 02482c70e4d84a..444c83f150f911 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php @@ -142,4 +142,31 @@ public function test_should_prettify_css_rule_output() { $this->assertSame( $expected, $css_rule->get_css( true ) ); } + + /** + * Tests that a string of multiple selectors is trimmed. + * + * @covers ::get_css + */ + public function test_should_trim_multiple_selectors() { + $selector = '.poirot, .poirot:active, #miss-marple > .st-mary-mead '; + $input_declarations = array( + 'margin-left' => '0', + 'font-family' => 'Detective Sans', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations_Gutenberg( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( $selector, $css_declarations ); + $expected = '.poirot, .poirot:active, #miss-marple > .st-mary-mead {margin-left:0;font-family:Detective Sans;}'; + + $this->assertSame( $expected, $css_rule->get_css(), 'Return value should be not prettified.' ); + + $expected_prettified = '.poirot, +.poirot:active, +#miss-marple > .st-mary-mead { + margin-left: 0; + font-family: Detective Sans; +}'; + + $this->assertSame( $expected_prettified, $css_rule->get_css( true ), 'Return value should be prettified with new lines and indents.' ); + } } From 516d05c1b370665e44b153be32a8f96d6ba902df Mon Sep 17 00:00:00 2001 From: Artemio Morales Date: Tue, 22 Nov 2022 05:13:29 -0500 Subject: [PATCH 032/384] Clarify explanation of how 'Convert to Links' works in Page List block (#45394) * Clarify explanation of how 'Convert to Links' works * Remove splitting of translated string * Update string to use createInterpolateElement to ensure proper translation * Simplify and clarify language in Page List edit modal * Remove tip from modal copy --- .../src/page-list/convert-to-links-modal.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js index bbb3e1e44e98b1..826539ec378348 100644 --- a/packages/block-library/src/page-list/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/convert-to-links-modal.js @@ -91,18 +91,13 @@ export default function ConvertToLinksModal( { onClose, clientId } ) {

{ __( - 'To edit this navigation menu, convert it to single page links. This allows you to add, re-order, remove items, or edit their labels.' - ) } -

-

- { __( - "Note: if you add new pages to your site, you'll need to add them to your navigation menu." + 'This menu is automatically kept in sync with pages on your site. You can manage the menu yourself by clicking customize below.' ) }

@@ -119,7 +114,7 @@ export default function ConvertToLinksModal( { onClose, clientId } ) { createBlock: create, } ) } > - { __( 'Convert' ) } + { __( 'Customize' ) }
From 6cedad20b0d96ac650b17d37b6071a5d7dbad881 Mon Sep 17 00:00:00 2001 From: Maggie Date: Tue, 22 Nov 2022 11:55:52 +0100 Subject: [PATCH 033/384] Include offcanvas specific styles (#45963) Co-authored-by: Dave Smith --- .../components/off-canvas-editor/style.scss | 398 +----------------- packages/block-editor/src/style.scss | 3 + 2 files changed, 4 insertions(+), 397 deletions(-) diff --git a/packages/block-editor/src/components/off-canvas-editor/style.scss b/packages/block-editor/src/components/off-canvas-editor/style.scss index 9782dc7027bc7c..7f22f6f5340cfd 100644 --- a/packages/block-editor/src/components/off-canvas-editor/style.scss +++ b/packages/block-editor/src/components/off-canvas-editor/style.scss @@ -1,397 +1 @@ -.block-editor-list-view-tree { - width: 100%; - border-collapse: collapse; - padding: 0; - margin: 0; - - // Move upwards when in modal. - .components-modal__content & { - margin: (-$grid-unit-15) (-$grid-unit-15 * 0.5) 0; - width: calc(100% + #{ $grid-unit-15 }); - } -} - -.block-editor-list-view-leaf { - // Use position relative for row animation. - position: relative; - - // The background has to be applied to the td, not tr, or border-radius won't work. - &.is-selected td { - background: var(--wp-admin-theme-color); - } - &.is-selected .block-editor-list-view-block-contents, - &.is-selected .components-button.has-icon { - color: $white; - } - &.is-selected .block-editor-list-view-block-contents { - // Hide selection styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - background: none; - color: $gray-900; - } - } - &.is-selected .block-editor-list-view-block-contents:focus { - &::after { - box-shadow: - inset 0 0 0 1px $white, - 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - } - } - &.is-selected .block-editor-list-view-block__menu:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) $white; - } - - &.is-dragging { - display: none; - } - - // Border radius for corners of the selected item. - &.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &.is-last-selected td:first-child { - border-bottom-left-radius: $radius-block-ui; - } - &.is-last-selected td:last-child { - border-bottom-right-radius: $radius-block-ui; - } - &.is-branch-selected:not(.is-selected) { - // Lighten a CSS variable without introducing a new SASS variable - background: - linear-gradient(transparentize($white, 0.1), transparentize($white, 0.1)), - linear-gradient(var(--wp-admin-theme-color), var(--wp-admin-theme-color)); - } - &.is-branch-selected.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &[aria-expanded="false"] { - &.is-branch-selected.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &.is-branch-selected.is-last-selected td:first-child { - border-bottom-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-last-selected td:last-child { - border-bottom-right-radius: $radius-block-ui; - } - } - &.is-branch-selected:not(.is-selected) td { - border-radius: 0; - } - - - // List View renders a fixed number of items and relies on each item having a fixed height of 36px. - // If this value changes, we should also change the itemHeight value set in useFixedWindowList. - // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. - .block-editor-list-view-block-contents { - display: flex; - align-items: center; - width: 100%; - height: auto; - padding: ($grid-unit-15 * 0.5) $grid-unit-05 ($grid-unit-15 * 0.5) 0; - text-align: left; - color: $gray-900; - border-radius: $radius-block-ui; - position: relative; - white-space: nowrap; - - &.is-dropping-before::before { - content: ""; - position: absolute; - pointer-events: none; - transition: border-color 0.1s linear, border-style 0.1s linear, box-shadow 0.1s linear; - top: -2px; - right: 0; - left: 0; - border-top: 4px solid var(--wp-admin-theme-color); - } - - .components-modal__content & { - padding-left: 0; - padding-right: 0; - } - } - - .block-editor-list-view-block-contents:focus { - box-shadow: none; - - &::after { - content: ""; - position: absolute; - top: 0; - right: -(24px + 5px); // Icon size + padding. - bottom: 0; - left: 0; - border-radius: inherit; - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - z-index: 2; - pointer-events: none; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } - } - } - // Fix focus styling width when one row has fewer cells. - &.has-single-cell .block-editor-list-view-block-contents:focus::after { - right: 0; - } - - .block-editor-list-view-block__menu:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - z-index: 1; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } - } - - &.is-visible .block-editor-list-view-block-contents { - opacity: 1; - @include edit-post__fade-in-animation; - } - - .block-editor-block-icon { - align-self: flex-start; - margin-right: $grid-unit-10; - width: $icon-size; - } - - .block-editor-list-view-block__menu-cell, - .block-editor-list-view-block__mover-cell, - .block-editor-list-view-block__contents-cell { - padding-top: 0; - padding-bottom: 0; - } - - .block-editor-list-view-block__menu-cell, - .block-editor-list-view-block__mover-cell { - line-height: 0; - width: $button-size; - vertical-align: middle; - @include reduce-motion("transition"); - - > * { - opacity: 0; - } - - // Show on hover, visible, and show above to keep the hit area size. - &:hover, - &.is-visible { - position: relative; - z-index: 1; - - > * { - opacity: 1; - @include edit-post__fade-in-animation; - } - } - - &, - .components-button.has-icon { - width: 24px; - min-width: 24px; - padding: 0; - } - } - - .block-editor-list-view-block__menu-cell { - padding-right: $grid-unit-05; - - .components-button.has-icon { - height: 24px; - } - } - - .block-editor-list-view-block__mover-cell-alignment-wrapper { - display: flex; - height: 100%; - flex-direction: column; - align-items: center; - } - - // Keep the tap target large but the focus target small. - .block-editor-block-mover-button { - position: relative; - width: $button-size; - height: $button-size-small; - - // Position the icon. - svg { - position: relative; - height: $button-size-small; - } - - &.is-up-button { - margin-top: -$grid-unit-15 * 0.5; - align-items: flex-end; - svg { - bottom: -$grid-unit-05; - } - } - - &.is-down-button { - margin-bottom: -$grid-unit-15 * 0.5; - align-items: flex-start; - svg { - top: -$grid-unit-05; - } - } - - // Tweak size and position of focus ring. - &::before { - height: 16px; - min-width: 100%; - left: 0; - right: 0; - } - } - - .block-editor-inserter__toggle { - background: $gray-900; - color: $white; - height: $grid-unit-30; - margin: 6px 6px 6px 1px; - min-width: $grid-unit-30; - - &:active { - color: $white; - } - } - - .block-editor-list-view-block-select-button__label-wrapper { - min-width: 120px; - } - - .block-editor-list-view-block-select-button__title { - flex: 1; - position: relative; - - .components-truncate { - position: absolute; - width: 100%; - transform: translateY(-50%); - } - } - - .block-editor-list-view-block-select-button__anchor-wrapper { - position: relative; - max-width: min(110px, 40%); - width: 100%; - } - - .block-editor-list-view-block-select-button__anchor { - position: absolute; - right: 0; - transform: translateY(-50%); - background: rgba($black, 0.1); - border-radius: $radius-block-ui; - padding: 2px 6px; - max-width: 100%; - box-sizing: border-box; - } - - &.is-selected .block-editor-list-view-block-select-button__anchor { - background: rgba($black, 0.3); - } - - .block-editor-list-view-block-select-button__lock { - line-height: 0; - width: 24px; - min-width: 24px; - margin-left: auto; - padding: 0; - vertical-align: middle; - } -} - -.block-editor-list-view-block-select-button__description, -.block-editor-list-view-appender__description { - display: none; -} - -.block-editor-list-view-block__contents-cell, -.block-editor-list-view-appender__cell { - .block-editor-list-view-block__contents-container, - .block-editor-list-view-appender__container { - display: flex; - } -} - -// Chevron container metrics. -.block-editor-list-view__expander { - height: $icon-size; - margin-left: $grid-unit-05; - width: $icon-size; -} - -// First level of indentation is aria-level 2, max indent is 8. -// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above. -$block-navigation-max-indent: 8; -.block-editor-list-view-leaf[aria-level] .block-editor-list-view__expander { - margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); -} - -.block-editor-list-view-leaf:not([aria-level="1"]) { - .block-editor-list-view__expander { - margin-right: 4px; - } -} - -@for $i from 0 to $block-navigation-max-indent { - .block-editor-list-view-leaf[aria-level="#{ $i + 1 }"] .block-editor-list-view__expander { - @if $i - 1 >= 0 { - margin-left: ( $icon-size * $i ) + 4 * ($i - 1); - } - @else { - margin-left: ( $icon-size * $i ); - } - } -} - -.block-editor-list-view-leaf .block-editor-list-view__expander { - visibility: hidden; -} - -// Point downwards when open. -.block-editor-list-view-leaf[aria-expanded="true"] .block-editor-list-view__expander svg { - visibility: visible; - transition: transform 0.2s ease; - transform: rotate(90deg); - @include reduce-motion("transition"); -} - -// Point rightwards when closed -.block-editor-list-view-leaf[aria-expanded="false"] .block-editor-list-view__expander svg { - visibility: visible; - transform: rotate(0deg); - transition: transform 0.2s ease; - @include reduce-motion("transition"); -} - -.block-editor-list-view-drop-indicator { - pointer-events: none; - - .block-editor-list-view-drop-indicator__line { - background: var(--wp-admin-theme-color); - height: $border-width; - } -} - -.block-editor-list-view-placeholder { - padding: 0; - margin: 0; - height: 36px; -} - +//Styles for off-canvas editor, remove this line when you add some css to this file! diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 853a030f045945..00737423611582 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -65,4 +65,7 @@ @import "./components/preview-options/style.scss"; @import "./components/spacing-sizes-control/style.scss"; +// Experiments. +@import "./components/off-canvas-editor/style.scss"; + @include wordpress-admin-schemes(); From 821db1c8fc598a8dd69b52a97fe843f691b5cfa4 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 22 Nov 2022 11:06:19 +0000 Subject: [PATCH 034/384] Navigation: Add label field to navigation link and navigation submenu (#45964) --- packages/block-library/src/navigation-link/edit.js | 8 ++++++++ packages/block-library/src/navigation-submenu/edit.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 30a2f9ebb4b520..0c08209130ba4c 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -737,6 +737,14 @@ export default function NavigationLinkEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index 9810c7ebab05c6..aa508e63a62262 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -578,6 +578,14 @@ export default function NavigationSubmenuEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { From 367c180b384f6656aa6fa5f6926e0c39ab073905 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 22 Nov 2022 13:02:41 +0000 Subject: [PATCH 035/384] Page List: If no parent page is set, still render all children (#45967) * Page List: If no parent page is set, still render all children * Fixes the block not rendering with no parent by correctly renaming the new parentPageID attribute. Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> * regenerated fixtures for new attribute name Co-authored-by: Andrei Draganescu Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- packages/block-library/src/page-list/block.json | 2 +- test/integration/fixtures/blocks/core__page-list.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 8650c9d7d32749..09e48ebda0f65e 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -429,7 +429,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** rootPageID +- **Attributes:** parentPageID ## Paragraph diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 144cecb428396c..2fc6993849d6f7 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -8,7 +8,7 @@ "keywords": [ "menu", "navigation" ], "textdomain": "default", "attributes": { - "rootPageID": { + "parentPageID": { "type": "integer", "default": 0 } diff --git a/test/integration/fixtures/blocks/core__page-list.json b/test/integration/fixtures/blocks/core__page-list.json index 52e22aa9059abd..b1992a437b8849 100644 --- a/test/integration/fixtures/blocks/core__page-list.json +++ b/test/integration/fixtures/blocks/core__page-list.json @@ -3,7 +3,7 @@ "name": "core/page-list", "isValid": true, "attributes": { - "rootPageID": 0 + "parentPageID": 0 }, "innerBlocks": [] } From d7e3cc12afac33be0b5b6eb9c85a27506cab4e5d Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:36:31 +0200 Subject: [PATCH 036/384] Lodash: Refactor editor away from _.pick() (#45944) --- .../provider/use-block-editor-settings.js | 97 +++++++++---------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 51d16ef1b6a7d6..0b69dcc66eb81c 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { pick } from 'lodash'; - /** * WordPress dependencies */ @@ -133,50 +128,54 @@ function useBlockEditorSettings( settings, hasTemplate ) { return useMemo( () => ( { - ...pick( settings, [ - '__experimentalBlockDirectory', - '__experimentalDiscussionSettings', - '__experimentalFeatures', - '__experimentalPreferredStyleVariations', - '__experimentalSetIsInserterOpened', - '__unstableGalleryWithImageBlocks', - 'alignWide', - 'allowedBlockTypes', - 'bodyPlaceholder', - 'canLockBlocks', - 'codeEditingEnabled', - 'colors', - 'disableCustomColors', - 'disableCustomFontSizes', - 'disableCustomSpacingSizes', - 'disableCustomGradients', - 'disableLayoutStyles', - 'enableCustomLineHeight', - 'enableCustomSpacing', - 'enableCustomUnits', - 'focusMode', - 'fontSizes', - 'gradients', - 'generateAnchors', - 'hasFixedToolbar', - 'isDistractionFree', - 'hasInlineToolbar', - 'imageDefaultSize', - 'imageDimensions', - 'imageEditing', - 'imageSizes', - 'isRTL', - 'keepCaretInsideBlock', - 'maxWidth', - 'onUpdateDefaultBlockStyles', - 'styles', - 'template', - 'templateLock', - 'titlePlaceholder', - 'supportsLayout', - 'widgetTypesToHideFromLegacyWidgetBlock', - '__unstableResolvedAssets', - ] ), + ...Object.fromEntries( + Object.entries( settings ).filter( ( [ key ] ) => + [ + '__experimentalBlockDirectory', + '__experimentalDiscussionSettings', + '__experimentalFeatures', + '__experimentalPreferredStyleVariations', + '__experimentalSetIsInserterOpened', + '__unstableGalleryWithImageBlocks', + 'alignWide', + 'allowedBlockTypes', + 'bodyPlaceholder', + 'canLockBlocks', + 'codeEditingEnabled', + 'colors', + 'disableCustomColors', + 'disableCustomFontSizes', + 'disableCustomSpacingSizes', + 'disableCustomGradients', + 'disableLayoutStyles', + 'enableCustomLineHeight', + 'enableCustomSpacing', + 'enableCustomUnits', + 'focusMode', + 'fontSizes', + 'gradients', + 'generateAnchors', + 'hasFixedToolbar', + 'isDistractionFree', + 'hasInlineToolbar', + 'imageDefaultSize', + 'imageDimensions', + 'imageEditing', + 'imageSizes', + 'isRTL', + 'keepCaretInsideBlock', + 'maxWidth', + 'onUpdateDefaultBlockStyles', + 'styles', + 'template', + 'templateLock', + 'titlePlaceholder', + 'supportsLayout', + 'widgetTypesToHideFromLegacyWidgetBlock', + '__unstableResolvedAssets', + ].includes( key ) + ) + ), mediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalReusableBlocks: reusableBlocks, __experimentalBlockPatterns: blockPatterns, From c75fc6d4b8f09dfc4fb2286814cbccc737ccc40f Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Tue, 22 Nov 2022 17:19:07 +0100 Subject: [PATCH 037/384] Update `gutenberg_get_global_stylesheet` to use `WP_Object_Cache` (#45679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- .../get-global-styles-and-settings.php | 85 -------------- .../class-wp-theme-json-resolver-6-2.php | 25 ++++ lib/compat/wordpress-6.2/default-filters.php | 10 ++ .../get-global-styles-and-settings.php | 110 ++++++++++++++++++ phpunit/wp-get-global-stylesheet-test.php | 98 ++++++++++++++++ 5 files changed, 243 insertions(+), 85 deletions(-) create mode 100644 phpunit/wp-get-global-stylesheet-test.php diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index 35c540ce1c57a6..fd6113c7405c4a 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -54,88 +54,3 @@ function ( $item ) { } } } - -/** - * Returns the stylesheet resulting of merging core, theme, and user data. - * - * @param array $types Types of styles to load. Optional. - * It accepts 'variables', 'styles', 'presets' as values. - * If empty, it'll load all for themes with theme.json support - * and only [ 'variables', 'presets' ] for themes without theme.json support. - * - * @return string Stylesheet. - */ -function gutenberg_get_global_stylesheet( $types = array() ) { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $can_use_cached = ( - ( empty( $types ) ) && - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() - ); - $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = wp_theme_has_theme_json(); - if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets', 'base-layout-styles' ); - } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); - } - - /* - * If variables are part of the stylesheet, - * we add them. - * - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); - $types = array_diff( $types, array( 'variables' ) ); - } - - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); - } - $stylesheet = $styles_variables . $styles_rest; - if ( $can_use_cached ) { - // Cache for a minute. - // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. - set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); - } - return $stylesheet; -} diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php index e10710e0f4709f..7adb37ffbd9e01 100644 --- a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php +++ b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php @@ -32,4 +32,29 @@ public static function theme_has_support() { return wp_theme_has_theme_json(); } + /** + * Private method to clean the cached data after an upgrade. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of bulk item update data. + */ + public static function _clean_cached_data_upon_upgrading( $upgrader, $options ) { + if ( 'update' !== $options['action'] ) { + return; + } + + if ( + 'core' === $options['type'] || + 'plugin' === $options['type'] || + // Clean cache only if the active theme was updated. + ( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) ) + ) { + static::clean_cached_data(); + } + } + } diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index 861b8a01421c62..927ff2bd12aa33 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -20,3 +20,13 @@ add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme', 10, 2 ); +add_action( 'save_post_wp_global_styles', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'activated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'deactivated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'upgrader_process_complete', array( 'WP_Theme_JSON_Resolver_Gutenberg', '_clean_cached_data_upon_upgrading', 10, 2 ) ); +add_action( 'save_post_wp_global_styles', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'switch_theme', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'start_previewing_theme', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'activated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'deactivated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'upgrader_process_complete', '_gutenberg_get_global_stylesheet_clean_cache_upon_upgrading', 10, 2 ); diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index be9781556868c0..5d37d3182ccc85 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -79,3 +79,113 @@ function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgr } } } + +/** + * Returns the stylesheet resulting of merging core, theme, and user data. + * + * @param array $types Types of styles to load. Optional. + * It accepts 'variables', 'styles', 'presets' as values. + * If empty, it'll load all for themes with theme.json support + * and only [ 'variables', 'presets' ] for themes without theme.json support. + * + * @return string Stylesheet. + */ +function gutenberg_get_global_stylesheet( $types = array() ) { + // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. + $can_use_cached = empty( $types ) && ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ); + $cache_key = 'gutenberg_get_global_stylesheet'; + $cache_group = 'theme_json'; + if ( $can_use_cached ) { + $cached = wp_cache_get( $cache_key, $cache_group ); + if ( $cached ) { + return $cached; + } + } + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $supports_theme_json = wp_theme_has_theme_json(); + if ( empty( $types ) && ! $supports_theme_json ) { + $types = array( 'variables', 'presets', 'base-layout-styles' ); + } elseif ( empty( $types ) ) { + $types = array( 'variables', 'styles', 'presets' ); + } + + /* + * If variables are part of the stylesheet, + * we add them. + * + * This is so themes without a theme.json still work as before 5.9: + * they can override the default presets. + * See https://core.trac.wordpress.org/ticket/54782 + */ + $styles_variables = ''; + if ( in_array( 'variables', $types, true ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); + $types = array_diff( $types, array( 'variables' ) ); + } + + /* + * For the remaining types (presets, styles), we do consider origins: + * + * - themes without theme.json: only the classes for the presets defined by core + * - themes with theme.json: the presets and styles classes, both from core and the theme + */ + $styles_rest = ''; + if ( ! empty( $types ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); + } + $styles_rest = $tree->get_stylesheet( $types, $origins ); + } + $stylesheet = $styles_variables . $styles_rest; + if ( $can_use_cached ) { + wp_cache_set( $cache_key, $stylesheet, $cache_group ); + } + return $stylesheet; +} + +/** + * Clean the cache used by the `gutenberg_get_global_stylesheet` function. + */ +function gutenberg_get_global_stylesheet_clean_cache() { + wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' ); +} + +/** + * Private function to clean the cache used by the `gutenberg_get_global_stylesheet` function after an upgrade. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of bulk item update data. + */ +function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, $options ) { + if ( 'update' !== $options['action'] ) { + return; + } + + if ( + 'core' === $options['type'] || + 'plugin' === $options['type'] || + // Clean cache only if the active theme was updated. + ( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) ) + ) { + gutenberg_get_global_stylesheet_clean_cache(); + } +} + diff --git a/phpunit/wp-get-global-stylesheet-test.php b/phpunit/wp-get-global-stylesheet-test.php new file mode 100644 index 00000000000000..cb4d0242ce3e3a --- /dev/null +++ b/phpunit/wp-get-global-stylesheet-test.php @@ -0,0 +1,98 @@ +user->create( + array( + 'role' => 'administrator', + 'user_email' => 'administrator@example.com', + ) + ); + } + + public function set_up() { + parent::set_up(); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + // Set up the new root. + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + + // Clear up the filters to modify the theme root. + remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + parent::tear_down(); + } + + public function filter_set_theme_root() { + return $this->theme_root; + } + + public function test_global_styles_user_cpt_change_invalidates_cached_stylesheet() { + add_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' ); + switch_theme( 'block-theme' ); + wp_set_current_user( self::$administrator_id ); + + $styles = gutenberg_get_global_stylesheet(); + $this->assertStringNotContainsString( 'background-color: hotpink;', $styles ); + + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true ); + $config = json_decode( $user_cpt['post_content'], true ); + $config['styles']['color']['background'] = 'hotpink'; + $user_cpt['post_content'] = wp_json_encode( $config ); + + wp_update_post( $user_cpt, true, false ); + + $styles = gutenberg_get_global_stylesheet(); + $this->assertStringContainsString( 'background-color: hotpink;', $styles ); + remove_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' ); + } +} From 6088f5cfaaaee5ee9d378514af0e0e416b1a227e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:39:51 -0700 Subject: [PATCH 038/384] Update which origins are queried for `gutenberg_get_global_settings` (#45971) Co-authored-by: Felix Arntz --- .../get-global-styles-and-settings.php | 29 -------------- .../get-global-styles-and-settings.php | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php index 9ed3e891182cb5..d4f1f9ef034924 100644 --- a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php @@ -5,35 +5,6 @@ * @package gutenberg */ -/** - * Function to get the settings resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific setting to retrieve. Optional. - * If empty, will return all settings. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the settings from. - * If empty, it'll return the settings for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The settings to retrieve. - */ -function gutenberg_get_global_settings( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); - } - $origin = 'custom'; - if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { - $origin = 'theme'; - } - $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); - return _wp_array_get( $settings, $path, $settings ); -} - /** * Function to get the styles resulting of merging core, theme, and user data. * diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index 5d37d3182ccc85..cee145acca5fc6 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -189,3 +189,41 @@ function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, } } +/** + * Function to get the settings resulting of merging core, theme, and user data. + * + * @param array $path Path to the specific setting to retrieve. Optional. + * If empty, will return all settings. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the settings from. + * If empty, it'll return the settings for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * + * @return array The settings to retrieve. + */ +function gutenberg_get_global_settings( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $new_path = array( 'blocks', $context['block_name'] ); + foreach ( $path as $subpath ) { + $new_path[] = $subpath; + } + $path = $new_path; + } + + // This is the default value when no origin is provided or when it is 'all'. + $origin = 'custom'; + if ( + ! wp_theme_has_theme_json() || + ( isset( $context['origin'] ) && 'base' === $context['origin'] ) + ) { + $origin = 'theme'; + } + + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); + return _wp_array_get( $settings, $path, $settings ); +} From 4183090b2030efce7f946f88cc3bdbb22ed6f9d5 Mon Sep 17 00:00:00 2001 From: Jonny Harris Date: Tue, 22 Nov 2022 20:30:38 +0000 Subject: [PATCH 039/384] Cleaner logic. (#45950) --- .../get-global-styles-and-settings.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index cee145acca5fc6..e74ef3144ef095 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -25,23 +25,23 @@ function wp_theme_has_theme_json() { * The reason not to store it as a boolean is to avoid working * with the $found parameter which apparently had some issues in some implementations * https://developer.wordpress.org/reference/functions/wp_cache_get/ + * + * Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. */ - if ( - // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( 0 === $theme_has_support || 1 === $theme_has_support ) - ) { + if ( ! WP_DEBUG && is_int( $theme_has_support ) ) { return (bool) $theme_has_support; } // Has the own theme a theme.json? - $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ) ? 1 : 0; + $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ); // Look up the parent if the child does not have a theme.json. - if ( 0 === $theme_has_support ) { - $theme_has_support = is_readable( get_template_directory() . '/theme.json' ) ? 1 : 0; + if ( ! $theme_has_support ) { + $theme_has_support = is_readable( get_template_directory() . '/theme.json' ); } + $theme_has_support = $theme_has_support ? 1 : 0; + wp_cache_set( $cache_key, $theme_has_support, $cache_group ); return (bool) $theme_has_support; From 7e76592a8fec0ee9e6a8dcb3cfda558fbc07ecec Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 15 Nov 2022 16:36:30 -0700 Subject: [PATCH 040/384] Tag Processor: Add ability to stop at tag closers, if requested `WP_HTML_Tag_Processor` introduces the ability to walk through an HTML document and modify attributes on tag openers. At times it could be useful to stop also at tag closers in order to perform augmented inspection and querying of a document. In this patch we're opening up a mode that allows just that; if querying with the `[ "tag_closers" => "visit" ]` query parrameter then in addition to stopping at tag openers, this will allow stopping at all tag tokens regardless of open or close status. --- .../html/class-wp-html-tag-processor.php | 137 +++++++++++++----- phpunit/html/wp-html-tag-processor-test.php | 56 +++++++ 2 files changed, 159 insertions(+), 34 deletions(-) diff --git a/lib/experimental/html/class-wp-html-tag-processor.php b/lib/experimental/html/class-wp-html-tag-processor.php index 6e72fc38ff4118..acdf8d97a95a96 100644 --- a/lib/experimental/html/class-wp-html-tag-processor.php +++ b/lib/experimental/html/class-wp-html-tag-processor.php @@ -221,6 +221,14 @@ class WP_HTML_Tag_Processor { */ private $sought_match_offset; + /** + * Whether to visit tag closers, e.g. , when walking an input document. + * + * @since 6.2.0 + * @var boolean + */ + private $stop_on_tag_closers; + /** * The updated HTML document. * @@ -276,6 +284,29 @@ class WP_HTML_Tag_Processor { */ private $tag_name_length; + /** + * Byte offset in input document where current tag token ends. + * + * Example: + * ``` + *
... + * 0 1 | + * 01234567890123456 + * --- tag name ends at 14 + * ``` + * + * @since 6.2.0 + * @var ?int + */ + private $tag_ends_at; + + /** + * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
. + * + * @var boolean + */ + private $is_closing_tag; + /** * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. * @@ -412,7 +443,16 @@ public function next_tag( $query = null ) { return false; } - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } + + $tag_ends_at = strpos( $this->html, '>', $this->parsed_bytes ); + if ( false === $tag_ends_at ) { + return false; + } + $this->tag_ends_at = $tag_ends_at; + $this->parsed_bytes = $tag_ends_at; if ( $this->matches() ) { ++$already_found; @@ -495,7 +535,9 @@ private function skip_rcdata( $tag_name ) { continue; } - $this->skip_tag_closer_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } $at = $this->parsed_bytes; if ( $at >= strlen( $this->html ) ) { return false; @@ -620,12 +662,14 @@ private function skip_script_data() { if ( $is_closing ) { $this->parsed_bytes = $at; - $this->skip_tag_closer_attributes(); - if ( $this->parsed_bytes >= $doc_length ) { return false; } + while ( $this->parse_next_attribute() ) { + continue; + } + if ( '>' === $html[ $this->parsed_bytes ] ) { ++$this->parsed_bytes; return true; @@ -656,6 +700,13 @@ private function parse_next_tag() { return false; } + if ( '/' === $this->html[ $at + 1 ] ) { + $this->is_closing_tag = true; + $at++; + } else { + $this->is_closing_tag = false; + } + /* * HTML tag names must start with [a-zA-Z] otherwise they are not tags. * For example, "<3" is rendered as text, not a tag opener. This means @@ -777,35 +828,12 @@ private function parse_next_tag() { return false; } - /** - * Parses all attributes of the current tag. - * - * @since 6.2.0 - */ - private function parse_tag_opener_attributes() { - while ( $this->parse_next_attribute() ) { - continue; - } - } - - /** - * Skips all attributes of the current tag. - * - * @since 6.2.0 - */ - private function skip_tag_closer_attributes() { - while ( $this->parse_next_attribute( 'tag-closer' ) ) { - continue; - } - } - /** * Parses the next attribute. * - * @param string $context tag-opener or tag-closer. * @since 6.2.0 */ - private function parse_next_attribute( $context = 'tag-opener' ) { + private function parse_next_attribute() { // Skip whitespace and slashes. $this->parsed_bytes += strspn( $this->html, " \t\f\r\n/", $this->parsed_bytes ); if ( $this->parsed_bytes >= strlen( $this->html ) ) { @@ -872,7 +900,7 @@ private function parse_next_attribute( $context = 'tag-opener' ) { return false; } - if ( 'tag-opener' !== $context ) { + if ( $this->is_closing_tag ) { return true; } @@ -914,6 +942,8 @@ private function after_tag() { $this->apply_attributes_updates(); $this->tag_name_starts_at = null; $this->tag_name_length = null; + $this->tag_ends_at = null; + $this->is_closing_tag = null; $this->attributes = array(); } @@ -1159,6 +1189,25 @@ public function get_tag() { return strtoupper( $tag_name ); } + /** + * Indicates if the current tag token is a tag closer. + * + * Example: + * + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === false; + * + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === true; + *
+ * + * @return bool + */ + public function is_tag_closer() { + return $this->is_closing_tag; + } + /** * Updates or creates a new attribute on the currently matched tag with the value passed. * @@ -1175,8 +1224,8 @@ public function get_tag() { * @throws Exception When WP_DEBUG is true and the attribute name is invalid. */ public function set_attribute( $name, $value ) { - if ( null === $this->tag_name_starts_at ) { - return; + if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) { + return false; } /* @@ -1286,8 +1335,8 @@ public function set_attribute( $name, $value ) { * @param string $name The attribute name to remove. */ public function remove_attribute( $name ) { - if ( ! isset( $this->attributes[ $name ] ) ) { - return; + if ( $this->is_closing_tag || ! isset( $this->attributes[ $name ] ) ) { + return false; } /* @@ -1316,6 +1365,10 @@ public function remove_attribute( $name ) { * @param string $class_name The class name to add. */ public function add_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::ADD_CLASS; } @@ -1329,6 +1382,10 @@ public function add_class( $class_name ) { * @param string $class_name The class name to remove. */ public function remove_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; } @@ -1392,7 +1449,9 @@ public function get_updated_html() { // Parse the attributes in the updated markup. $this->attributes = array(); - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } return $this->html; } @@ -1407,6 +1466,7 @@ public function get_updated_html() { * * @type string|null $tag_name Which tag to find, or `null` for "any tag." * @type string|null $class_name Tag must contain this class name to match. + * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. * } */ private function parse_query( $query ) { @@ -1418,6 +1478,7 @@ private function parse_query( $query ) { $this->sought_tag_name = null; $this->sought_class_name = null; $this->sought_match_offset = 1; + $this->stop_on_tag_closers = false; // A single string value means "find the tag of this name". if ( is_string( $query ) ) { @@ -1441,6 +1502,10 @@ private function parse_query( $query ) { if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) { $this->sought_match_offset = $query['match_offset']; } + + if ( isset( $query['tag_closers'] ) ) { + $this->stop_on_tag_closers = 'visit' === $query['tag_closers']; + } } @@ -1452,6 +1517,10 @@ private function parse_query( $query ) { * @return boolean */ private function matches() { + if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { + return false; + } + // Do we match a case-insensitive HTML tag name? if ( null !== $this->sought_tag_name ) { /* diff --git a/phpunit/html/wp-html-tag-processor-test.php b/phpunit/html/wp-html-tag-processor-test.php index e66b1d50758d0b..273cbddddea4bf 100644 --- a/phpunit/html/wp-html-tag-processor-test.php +++ b/phpunit/html/wp-html-tag-processor-test.php @@ -237,6 +237,35 @@ public function test_next_tag_should_return_false_for_a_non_existing_tag() { $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); } + /** + * @covers next_tag + * @covers is_tag_closer + */ + public function test_next_tag_should_stop_on_closers_only_when_requested() { + $p = new WP_HTML_Tag_Processor( '
' ); + $this->assertTrue( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Did not find desired tag opener' ); + $this->assertFalse( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Visited an unwanted tag, a tag closer' ); + + $p = new WP_HTML_Tag_Processor( '
' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertFalse( $p->is_tag_closer(), 'Indicated a tag opener is a tag closer' ); + $this->assertTrue( + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ), + 'Did not stop at desired tag closer' + ); + $this->assertTrue( $p->is_tag_closer(), 'Indicated a tag closer is a tag opener' ); + } + /** * @ticket 56299 * @@ -255,6 +284,33 @@ public function test_set_attribute_on_a_non_existing_tag_does_not_change_the_mar ); } + public function test_attribute_ops_on_tag_closer_do_not_change_the_markup() { + $p = new WP_HTML_Tag_Processor( '
' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertFalse( $p->is_tag_closer(), 'Skipped tag opener' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertTrue( $p->is_tag_closer(), 'Skipped tag closer' ); + $this->assertFalse( $p->set_attribute( 'id', 'test' ), "Allowed setting an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->remove_attribute( 'invalid-id' ), "Allowed removing an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->add_class( 'sneaky' ), "Allowed adding a class on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->remove_class( 'not-appearing-in-this-test' ), "Allowed removing a class on a tag closer when it shouldn't have" ); + $this->assertSame( + '
', + $p->get_updated_html(), + 'Calling get_updated_html after updating a non-existing tag returned an HTML that was different from the original HTML' + ); + } + /** * Passing a double quote inside of an attribute values could lead to an XSS attack as follows: * From d98e24e94bd6208be85196d409c8e63c8db5627b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 23 Nov 2022 18:59:44 +1300 Subject: [PATCH 041/384] Colorize template parts and Reusable blocks (#45473) --- packages/base-styles/_colors.scss | 7 ++++++ .../src/components/block-card/index.js | 12 +++++++++- .../src/components/block-card/style.scss | 4 ++++ .../block-content-overlay/style.scss | 8 ++++++- .../src/components/block-toolbar/index.js | 17 +++++++++---- .../src/components/block-toolbar/style.scss | 10 ++++++++ .../components/inserter-list-item/index.js | 12 +++++++++- .../components/inserter-list-item/style.scss | 5 ++++ .../src/components/list-view/block.js | 3 +++ .../src/components/list-view/branch.js | 7 ++++++ .../src/components/list-view/style.scss | 24 +++++++++++++++---- .../use-block-display-information/index.js | 19 +++++++++++---- packages/block-library/src/block/editor.scss | 17 +++++++++++++ .../src/template-part/editor.scss | 22 +++++++++++++++-- packages/blocks/src/api/registration.js | 2 +- .../document-actions/index.js | 23 +++++++++++------- .../document-actions/style.scss | 8 ++++++- .../components/template-details/style.scss | 4 ++++ 18 files changed, 173 insertions(+), 31 deletions(-) diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss index 03f6bfbdd566d4..5847a66f8b2435 100644 --- a/packages/base-styles/_colors.scss +++ b/packages/base-styles/_colors.scss @@ -1,3 +1,5 @@ +@import "./functions"; + /** * Colors */ @@ -24,3 +26,8 @@ $light-gray-placeholder: rgba($white, 0.65); $alert-yellow: #f0b849; $alert-red: #cc1818; $alert-green: #4ab866; + +:root { + --wp-block-synced-color: #7a00df; + --wp-block-synced-color--rgb: #{hex-to-rgb(#7a00df)}; +} diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 6362b1b4ca2c7c..afda93d5ece7af 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -17,6 +22,7 @@ function BlockCard( { blockType, parentBlockClientId, handleBackButton, + isSynced, } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -30,7 +36,11 @@ function BlockCard( { window?.__experimentalEnableOffCanvasNavigationEditor === true; return ( -
+
{ isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( ) } { ! canOnlyChangeValues && ( @@ -454,19 +482,40 @@ export default function PaletteEdit( { isGradient={ isGradient } /> ) } + { ! isEditing && editingElement !== null && ( + setEditingElement( null ) } + onChange={ ( newElement ) => { + debounceOnChange( + elements.map( + ( currentElement, currentIndex ) => { + if ( + currentIndex === editingElement + ) { + return newElement; + } + return currentElement; + } + ) + ); + } } + element={ elements[ editingElement ] } + /> + ) } { ! isEditing && ( isGradient ? ( {} } + onChange={ onSelectPaletteItem } clearable={ false } disableCustomGradients={ true } /> ) : ( {} } + onChange={ onSelectPaletteItem } clearable={ false } disableCustomColors={ true } /> From 2982e4f8c8af9f4e7cceba100c1fb2606262dc3a Mon Sep 17 00:00:00 2001 From: brookewp <35543432+brookewp@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:33:18 -0800 Subject: [PATCH 055/384] ComboboxControl: replace margin overides with new opt-in prop (#45796) --- packages/block-library/src/avatar/user-control.js | 1 + packages/block-library/src/post-author/edit.js | 1 + packages/editor/src/components/page-attributes/parent.js | 1 + packages/editor/src/components/post-author/combobox.js | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/block-library/src/avatar/user-control.js b/packages/block-library/src/avatar/user-control.js index 49370b39716eff..598c05011eaed7 100644 --- a/packages/block-library/src/avatar/user-control.js +++ b/packages/block-library/src/avatar/user-control.js @@ -33,6 +33,7 @@ function UserControl( { value, onChange } ) { return ( Date: Wed, 23 Nov 2022 15:33:51 -0800 Subject: [PATCH 056/384] FocalPointPicker: add new opt-in prop (#45958) --- packages/block-library/src/cover/edit/inspector-controls.js | 1 + packages/block-library/src/media-text/edit.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index cfcf012badd62c..98747988c8f409 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -162,6 +162,7 @@ export default function CoverInspectorControls( { ) } { showFocalPointPicker && ( Date: Thu, 24 Nov 2022 12:14:00 +1100 Subject: [PATCH 057/384] Min Height: Add height control component with slider (#45875) * Min Height: Add height control component with slider * Fix typo for rem unit Co-authored-by: Ramon * Add some handling for switching between unit types * Add storybook example Co-authored-by: Ramon --- .../src/components/height-control/index.js | 123 ++++++++++++++++++ .../height-control/stories/index.js | 21 +++ .../src/components/height-control/style.scss | 5 + packages/block-editor/src/components/index.js | 1 + packages/block-editor/src/hooks/dimensions.js | 1 - packages/block-editor/src/hooks/min-height.js | 21 +-- packages/block-editor/src/style.scss | 1 + .../global-styles/dimensions-panel.js | 7 +- 8 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 packages/block-editor/src/components/height-control/index.js create mode 100644 packages/block-editor/src/components/height-control/stories/index.js create mode 100644 packages/block-editor/src/components/height-control/style.scss diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js new file mode 100644 index 00000000000000..cc1eea0b4bad86 --- /dev/null +++ b/packages/block-editor/src/components/height-control/index.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { + BaseControl, + RangeControl, + Flex, + FlexItem, + __experimentalSpacer as Spacer, + __experimentalUseCustomUnits as useCustomUnits, + __experimentalUnitControl as UnitControl, + __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useSetting from '../use-setting'; + +const RANGE_CONTROL_CUSTOM_SETTINGS = { + px: { max: 1000, step: 1 }, + '%': { max: 100, step: 1 }, + vw: { max: 100, step: 1 }, + vh: { max: 100, step: 1 }, + em: { max: 50, step: 0.1 }, + rem: { max: 50, step: 0.1 }, +}; + +export default function HeightControl( { + onChange, + label = __( 'Height' ), + value, +} ) { + const customRangeValue = parseFloat( value ); + + const units = useCustomUnits( { + availableUnits: useSetting( 'spacing.units' ) || [ + '%', + 'px', + 'em', + 'rem', + 'vh', + 'vw', + ], + } ); + + const selectedUnit = + useMemo( + () => parseQuantityAndUnitFromRawValue( value ), + [ value ] + )[ 1 ] || + units[ 0 ]?.value || + 'px'; + + const handleSliderChange = ( next ) => { + onChange( [ next, selectedUnit ].join( '' ) ); + }; + + const handleUnitChange = ( newUnit ) => { + // Attempt to smooth over differences between currentUnit and newUnit. + // This should slightly improve the experience of switching between unit types. + const [ currentValue, currentUnit ] = + parseQuantityAndUnitFromRawValue( value ); + + if ( [ 'em', 'rem' ].includes( newUnit ) && currentUnit === 'px' ) { + // Convert pixel value to an approximate of the new unit, assuming a root size of 16px. + onChange( ( currentValue / 16 ).toFixed( 2 ) + newUnit ); + } else if ( + [ 'em', 'rem' ].includes( currentUnit ) && + newUnit === 'px' + ) { + // Convert to pixel value assuming a root size of 16px. + onChange( Math.round( currentValue * 16 ) + newUnit ); + } else if ( + [ 'vh', 'vw', '%' ].includes( newUnit ) && + currentValue > 100 + ) { + // When converting to `vh`, `vw`, or `%` units, cap the new value at 100. + onChange( 100 + newUnit ); + } + }; + + return ( +
+ + { label } + + + + + + + + + + + +
+ ); +} diff --git a/packages/block-editor/src/components/height-control/stories/index.js b/packages/block-editor/src/components/height-control/stories/index.js new file mode 100644 index 00000000000000..f4b586a96b0e33 --- /dev/null +++ b/packages/block-editor/src/components/height-control/stories/index.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import HeightControl from '../'; + +export default { + component: HeightControl, + title: 'BlockEditor/HeightControl', +}; + +const Template = ( props ) => { + const [ value, setValue ] = useState(); + return ; +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/height-control/style.scss b/packages/block-editor/src/components/height-control/style.scss new file mode 100644 index 00000000000000..add0866835f767 --- /dev/null +++ b/packages/block-editor/src/components/height-control/style.scss @@ -0,0 +1,5 @@ +.block-editor-height-control { + border: 0; + margin: 0; + padding: 0; +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 7743a98066c437..03ce8f3880ad32 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -53,6 +53,7 @@ export { default as __experimentalColorGradientControl } from './colors-gradient export { default as __experimentalColorGradientSettingsDropdown } from './colors-gradients/dropdown'; export { default as __experimentalPanelColorGradientSettings } from './colors-gradients/panel-color-gradient-settings'; export { default as __experimentalUseMultipleOriginColorsAndGradients } from './colors-gradients/use-multiple-origin-colors-and-gradients'; +export { default as __experimentalHeightControl } from './height-control'; export { default as __experimentalImageEditor, ImageEditingProvider as __experimentalImageEditingProvider, diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index 44a9fd83278a95..3b33df400fcfc3 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -182,7 +182,6 @@ export function DimensionsPanel( props ) { ) } { ! isMinHeightDisabled && ( hasMinHeightValue( props ) } label={ __( 'Min. height' ) } onDeselect={ () => resetMinHeight( props ) } diff --git a/packages/block-editor/src/hooks/min-height.js b/packages/block-editor/src/hooks/min-height.js index 3167edba8a8293..e123f0cee98b22 100644 --- a/packages/block-editor/src/hooks/min-height.js +++ b/packages/block-editor/src/hooks/min-height.js @@ -2,16 +2,13 @@ * WordPress dependencies */ import { getBlockSupport } from '@wordpress/blocks'; -import { - __experimentalUseCustomUnits as useCustomUnits, - __experimentalUnitControl as UnitControl, -} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import useSetting from '../components/use-setting'; +import HeightControl from '../components/height-control'; import { DIMENSIONS_SUPPORT_KEY } from './dimensions'; import { cleanEmptyObject } from './utils'; @@ -81,17 +78,6 @@ export function MinHeightEdit( props ) { setAttributes, } = props; - const units = useCustomUnits( { - availableUnits: useSetting( 'dimensions.units' ) || [ - '%', - 'px', - 'em', - 'rem', - 'vh', - 'vw', - ], - } ); - if ( useIsMinHeightDisabled( props ) ) { return null; } @@ -109,13 +95,10 @@ export function MinHeightEdit( props ) { }; return ( - ); } diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 00737423611582..6db197a389d217 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -32,6 +32,7 @@ @import "./components/date-format-picker/style.scss"; @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; +@import "./components/height-control/style.scss"; @import "./components/image-size-control/style.scss"; @import "./components/inner-blocks/style.scss"; @import "./components/inserter-list-item/style.scss"; diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js index d27a109e2b752a..1b88bc4074dc87 100644 --- a/packages/edit-site/src/components/global-styles/dimensions-panel.js +++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js @@ -18,6 +18,7 @@ import { } from '@wordpress/components'; import { __experimentalUseCustomSides as useCustomSides, + __experimentalHeightControl as HeightControl, __experimentalSpacingSizesControl as SpacingSizesControl, } from '@wordpress/block-editor'; import { Icon, positionCenter, stretchWide } from '@wordpress/icons'; @@ -556,19 +557,15 @@ export default function DimensionsPanel( { name } ) { ) } { showMinHeightControl && ( - ) } From 08ae94ce2408bb486f1d6c669e35cfe6c2691c4b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 24 Nov 2022 15:36:27 +1300 Subject: [PATCH 058/384] Pass the is-synced classname into the BlockCard instead of the isSynced prop (#46021) --- packages/block-editor/src/components/block-card/index.js | 8 ++------ .../block-editor/src/components/block-inspector/index.js | 6 +++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index afda93d5ece7af..c85fd3ff21dd37 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -22,7 +22,7 @@ function BlockCard( { blockType, parentBlockClientId, handleBackButton, - isSynced, + className, } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -36,11 +36,7 @@ function BlockCard( { window?.__experimentalEnableOffCanvasNavigationEditor === true; return ( -
+
{ isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( - ); - } ) } -
-
- ); -} - export default function NavigationLinkEdit( { attributes, isSelected, @@ -460,7 +257,7 @@ export default function NavigationLinkEdit( { opensInNewTab, title: label && navStripHTML( label ), // don't allow HTML to display inside the }; - const { saveEntityRecord } = useDispatch( coreStore ); + const { replaceBlock, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); const [ isLinkOpen, setIsLinkOpen ] = useState( false ); @@ -618,33 +415,6 @@ export default function NavigationLinkEdit( { userCanCreate = postsPermissions.canCreate; } - async function handleCreate( pageTitle ) { - const postType = type || 'page'; - - const page = await saveEntityRecord( 'postType', postType, { - title: pageTitle, - status: 'draft', - } ); - - return { - id: page.id, - type: postType, - // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities) - // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code. - // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts. - // e.g. - // - title.raw = "Yes & No" - // - title.rendered = "Yes & No" - // - decodeEntities( title.rendered ) = "Yes & No" - // See: - // - https://github.com/WordPress/gutenberg/pull/41063 - // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218 - title: decodeEntities( page.title.rendered ), - url: page.link, - kind: 'post-type', - }; - } - const { textColor, customTextColor, @@ -867,7 +637,7 @@ export default function NavigationLinkEdit( { // Ideally they would be stored in a raw, unescaped form. // Unescape is used here to "recover" the escaped characters // so they display without encoding. - // See `updateNavigationLinkBlockAttributes` for more details. + // See `updateAttributes` for more details. `${ unescape( label ) } ${ placeholderText }`.trim() @@ -883,64 +653,22 @@ export default function NavigationLinkEdit( { ) } { isLinkOpen && ( - setIsLinkOpen( false ) } anchor={ popoverAnchor } - shift - > - { - let format; - if ( type === 'post' ) { - /* translators: %s: search term. */ - format = __( - 'Create draft post: %s' - ); - } else { - /* translators: %s: search term. */ - format = __( - 'Create draft page: %s' - ); - } - return createInterpolateElement( - sprintf( format, searchTerm ), - { mark: } - ); - } } - noDirectEntry={ !! type } - noURLSuggestion={ !! type } - suggestionsQuery={ getSuggestionsQuery( - type, - kind - ) } - onChange={ ( updatedValue ) => - updateNavigationLinkBlockAttributes( - updatedValue, - setAttributes, - attributes - ) - } - onRemove={ removeLink } - renderControlBottom={ - ! url - ? () => ( - - ) - : null - } - /> - + hasCreateSuggestion={ userCanCreate } + onRemove={ removeLink } + onChange={ ( updatedValue ) => { + updateAttributes( + updatedValue, + setAttributes, + attributes + ); + } } + /> ) }
diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js new file mode 100644 index 00000000000000..5dfc3cffc1f31b --- /dev/null +++ b/packages/block-library/src/navigation-link/link-ui.js @@ -0,0 +1,202 @@ +/** + * WordPress dependencies + */ +import { Popover, Button } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { + __experimentalLinkControl as LinkControl, + BlockIcon, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { createInterpolateElement } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { switchToBlockType } from '@wordpress/blocks'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Given the Link block's type attribute, return the query params to give to + * /wp/v2/search. + * + * @param {string} type Link block's type attribute. + * @param {string} kind Link block's entity of kind (post-type|taxonomy) + * @return {{ type?: string, subtype?: string }} Search query params. + */ +export function getSuggestionsQuery( type, kind ) { + switch ( type ) { + case 'post': + case 'page': + return { type: 'post', subtype: type }; + case 'category': + return { type: 'term', subtype: 'category' }; + case 'tag': + return { type: 'term', subtype: 'post_tag' }; + case 'post_format': + return { type: 'post-format' }; + default: + if ( kind === 'taxonomy' ) { + return { type: 'term', subtype: type }; + } + if ( kind === 'post-type' ) { + return { type: 'post', subtype: type }; + } + return {}; + } +} + +/** + * Add transforms to Link Control + * + * @param {Object} props Component props. + * @param {string} props.clientId Block client ID. + */ +function LinkControlTransforms( { clientId } ) { + const { getBlock, blockTransforms } = useSelect( + ( select ) => { + const { + getBlock: _getBlock, + getBlockRootClientId, + getBlockTransformItems, + } = select( blockEditorStore ); + + return { + getBlock: _getBlock, + blockTransforms: getBlockTransformItems( + _getBlock( clientId ), + getBlockRootClientId( clientId ) + ), + }; + }, + [ clientId ] + ); + + const { replaceBlock } = useDispatch( blockEditorStore ); + + const featuredBlocks = [ + 'core/site-logo', + 'core/social-links', + 'core/search', + ]; + const transforms = blockTransforms.filter( ( item ) => { + return featuredBlocks.includes( item.name ); + } ); + + if ( ! transforms?.length ) { + return null; + } + + return ( +
+

+ { __( 'Transform' ) } +

+
+ { transforms.map( ( item, index ) => { + return ( + + ); + } ) } +
+
+ ); +} + +export function LinkUI( props ) { + const { saveEntityRecord } = useDispatch( coreStore ); + + async function handleCreate( pageTitle ) { + const postType = props.linkAttributes.type || 'page'; + + const page = await saveEntityRecord( 'postType', postType, { + title: pageTitle, + status: 'draft', + } ); + + return { + id: page.id, + type: postType, + // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities) + // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code. + // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts. + // e.g. + // - title.raw = "Yes & No" + // - title.rendered = "Yes & No" + // - decodeEntities( title.rendered ) = "Yes & No" + // See: + // - https://github.com/WordPress/gutenberg/pull/41063 + // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218 + title: decodeEntities( page.title.rendered ), + url: page.link, + kind: 'post-type', + }; + } + + return ( + + { + let format; + + if ( props.linkAttributes.type === 'post' ) { + /* translators: %s: search term. */ + format = __( 'Create draft post: %s' ); + } else { + /* translators: %s: search term. */ + format = __( 'Create draft page: %s' ); + } + + return createInterpolateElement( + sprintf( format, searchTerm ), + { + mark: , + } + ); + } } + noDirectEntry={ !! props.linkAttributes.type } + noURLSuggestion={ !! props.linkAttributes.type } + suggestionsQuery={ getSuggestionsQuery( + props.linkAttributes.type, + props.linkAttributes.kind + ) } + onChange={ props.onChange } + onRemove={ props.onRemove } + renderControlBottom={ + ! props.linkAttributes.url + ? () => ( + + ) + : null + } + /> + + ); +} diff --git a/packages/block-library/src/navigation-link/test/edit.js b/packages/block-library/src/navigation-link/test/edit.js index 8d052a5e3f133a..ec396a64b20586 100644 --- a/packages/block-library/src/navigation-link/test/edit.js +++ b/packages/block-library/src/navigation-link/test/edit.js @@ -1,10 +1,10 @@ /** * Internal dependencies */ -import { updateNavigationLinkBlockAttributes } from '../edit'; +import { updateAttributes } from '../update-attributes'; describe( 'edit', () => { - describe( 'updateNavigationLinkBlockAttributes', () => { + describe( 'updateAttributes', () => { // Data shapes are linked to fetchLinkSuggestions from // core-data/src/fetch/__experimental-fetch-link-suggestions.js. it( 'can update a post link', () => { @@ -18,10 +18,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 1337, label: 'Menu Test', @@ -42,10 +39,7 @@ describe( 'edit', () => { type: 'page', url: 'http://wordpress.local/sample-page/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 2, kind: 'post-type', @@ -66,10 +60,7 @@ describe( 'edit', () => { type: 'post_tag', url: 'http://wordpress.local/tag/bar/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 15, kind: 'taxonomy', @@ -90,10 +81,7 @@ describe( 'edit', () => { type: 'category', url: 'http://wordpress.local/category/cats/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 9, kind: 'taxonomy', @@ -114,10 +102,7 @@ describe( 'edit', () => { type: 'portfolio', url: 'http://wordpress.local/portfolio/fall/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 131, kind: 'post-type', @@ -138,10 +123,7 @@ describe( 'edit', () => { type: 'portfolio_tag', url: 'http://wordpress.local/portfolio_tag/PortfolioTag/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 4, kind: 'taxonomy', @@ -162,10 +144,7 @@ describe( 'edit', () => { type: 'portfolio_category', url: 'http://wordpress.local/portfolio_category/Portfolio-category/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 2, kind: 'taxonomy', @@ -186,10 +165,7 @@ describe( 'edit', () => { type: 'post-format', url: 'http://wordpress.local/type/video/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); // post_format returns a slug ID value from the Search API // we do not persist this ID since we expect this value to be a post or term ID. expect( setAttributes ).toHaveBeenCalledWith( { @@ -208,10 +184,7 @@ describe( 'edit', () => { opensInNewTab: false, url: 'www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, url: 'www.wordpress.org', @@ -229,10 +202,7 @@ describe( 'edit', () => { type: 'URL', url: 'http://www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'www.wordpress.org', @@ -250,10 +220,7 @@ describe( 'edit', () => { type: 'mailto', url: 'mailto:foo@example.com', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'mailto:foo@example.com', @@ -272,10 +239,7 @@ describe( 'edit', () => { type: 'internal', url: '#foo', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: '#foo', @@ -294,10 +258,7 @@ describe( 'edit', () => { type: 'tel', url: 'tel:5555555', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'tel:5555555', @@ -319,10 +280,7 @@ describe( 'edit', () => { type: 'URL', url: 'https://www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'www.wordpress.org', @@ -339,10 +297,7 @@ describe( 'edit', () => { type: 'URL', url: 'wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -359,10 +314,7 @@ describe( 'edit', () => { type: 'URL', url: 'https://wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -380,10 +332,7 @@ describe( 'edit', () => { type: 'URL', url: 'http://wordpress.org/?s=<>', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -408,11 +357,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes, - mockState - ); + updateAttributes( linkSuggestion, setAttributes, mockState ); expect( mockState ).toEqual( { id: 1337, label: 'Menu Test', @@ -422,7 +367,7 @@ describe( 'edit', () => { url: 'https://wordpress.local/menu-test/', } ); // Click on the existing link control, and toggle opens new tab. - updateNavigationLinkBlockAttributes( + updateAttributes( { url: 'https://wordpress.local/menu-test/', opensInNewTab: true, @@ -453,11 +398,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes, - mockState - ); + updateAttributes( linkSuggestion, setAttributes, mockState ); expect( mockState ).toEqual( { id: 1337, label: 'Menu Test', @@ -467,7 +408,7 @@ describe( 'edit', () => { url: 'https://wordpress.local/menu-test/', } ); // Click on the existing link control, and toggle opens new tab. - updateNavigationLinkBlockAttributes( + updateAttributes( { url: 'https://wordpress.local/foo/', opensInNewTab: false, diff --git a/packages/block-library/src/navigation-link/update-attributes.js b/packages/block-library/src/navigation-link/update-attributes.js new file mode 100644 index 00000000000000..247498107ac35b --- /dev/null +++ b/packages/block-library/src/navigation-link/update-attributes.js @@ -0,0 +1,103 @@ +/** + * External dependencies + */ +import escapeHtml from 'escape-html'; + +/** + * WordPress dependencies + */ +import { safeDecodeURI } from '@wordpress/url'; + +/** + * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind + */ +/** + * Navigation Link Block Attributes + * + * @typedef {Object} WPNavigationLinkBlockAttributes + * + * @property {string} [label] Link text. + * @property {WPNavigationLinkKind} [kind] Kind is used to differentiate between term and post ids to check post draft status. + * @property {string} [type] The type such as post, page, tag, category and other custom types. + * @property {string} [rel] The relationship of the linked URL. + * @property {number} [id] A post or term id. + * @property {boolean} [opensInNewTab] Sets link target to _blank when true. + * @property {string} [url] Link href. + * @property {string} [title] Link title attribute. + */ +/** + * Link Control onChange handler that updates block attributes when a setting is changed. + * + * @param {Object} updatedValue New block attributes to update. + * @param {Function} setAttributes Block attribute update function. + * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes. + * + */ + +export const updateAttributes = ( + updatedValue = {}, + setAttributes, + blockAttributes = {} +) => { + const { + label: originalLabel = '', + kind: originalKind = '', + type: originalType = '', + } = blockAttributes; + + const { + title: newLabel = '', // the title of any provided Post. + url: newUrl = '', + opensInNewTab, + id, + kind: newKind = originalKind, + type: newType = originalType, + } = updatedValue; + + const newLabelWithoutHttp = newLabel.replace( /http(s?):\/\//gi, '' ); + const newUrlWithoutHttp = newUrl.replace( /http(s?):\/\//gi, '' ); + + const useNewLabel = + newLabel && + newLabel !== originalLabel && + // LinkControl without the title field relies + // on the check below. Specifically, it assumes that + // the URL is the same as a title. + // This logic a) looks suspicious and b) should really + // live in the LinkControl and not here. It's a great + // candidate for future refactoring. + newLabelWithoutHttp !== newUrlWithoutHttp; + + // Unfortunately this causes the escaping model to be inverted. + // The escaped content is stored in the block attributes (and ultimately in the database), + // and then the raw data is "recovered" when outputting into the DOM. + // It would be preferable to store the **raw** data in the block attributes and escape it in JS. + // Why? Because there isn't one way to escape data. Depending on the context, you need to do + // different transforms. It doesn't make sense to me to choose one of them for the purposes of storage. + // See also: + // - https://github.com/WordPress/gutenberg/pull/41063 + // - https://github.com/WordPress/gutenberg/pull/18617. + const label = useNewLabel + ? escapeHtml( newLabel ) + : originalLabel || escapeHtml( newUrlWithoutHttp ); + + // In https://github.com/WordPress/gutenberg/pull/24670 we decided to use "tag" in favor of "post_tag" + const type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' ); + + const isBuiltInType = + [ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1; + + const isCustomLink = + ( ! newKind && ! isBuiltInType ) || newKind === 'custom'; + const kind = isCustomLink ? 'custom' : newKind; + + setAttributes( { + // Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string. + ...( newUrl && { url: encodeURI( safeDecodeURI( newUrl ) ) } ), + ...( label && { label } ), + ...( undefined !== opensInNewTab && { opensInNewTab } ), + ...( id && Number.isInteger( id ) && { id } ), + ...( kind && { kind } ), + ...( type && type !== 'URL' && { type } ), + } ); +}; From ee90c2b1df7cb59e1f6ea3e2aaeb3b15386396ac Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 24 Nov 2022 15:37:25 +0000 Subject: [PATCH 072/384] Navigation: Hide the create new menu button if the experiment is enabled (#46019) --- .../src/navigation/edit/navigation-menu-selector.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/edit/navigation-menu-selector.js b/packages/block-library/src/navigation/edit/navigation-menu-selector.js index 9817934c3a2b16..4b1182475e8d5a 100644 --- a/packages/block-library/src/navigation/edit/navigation-menu-selector.js +++ b/packages/block-library/src/navigation/edit/navigation-menu-selector.js @@ -143,7 +143,11 @@ function NavigationMenuSelector( { }, }; - if ( ! hasNavigationMenus && ! hasClassicMenus ) { + if ( + ! hasNavigationMenus && + ! hasClassicMenus && + ! isOffCanvasNavigationEditorEnabled + ) { return ( + ) } + /> + +
+ + +
+ ) } + { isMobile && ( + + ) } + + ); +} + +function MediaTabNavigation( { onInsert, rootClientId, mediaCategories } ) { + return ( + + + + { mediaCategories.map( ( category ) => ( + + + { category.label } + + + + ) ) } + + + { mediaCategories.map( ( category ) => ( + + + { __( 'Back' ) } + + + + ) ) } + + ); +} + +export default MediaTab; diff --git a/packages/block-editor/src/components/inserter/media-tab/utils.js b/packages/block-editor/src/components/inserter/media-tab/utils.js new file mode 100644 index 00000000000000..f6a18514d728c7 --- /dev/null +++ b/packages/block-editor/src/components/inserter/media-tab/utils.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +const mediaTypeTag = { image: 'img', video: 'video', audio: 'audio' }; + +export function getBlockAndPreviewFromMedia( media, mediaType ) { + // Add the common attributes between the different media types. + const attributes = { + id: media.id, + }; + // Some props are named differently between the Media REST API and Media Library API. + // For example `source_url` is used in the former and `url` is used in the latter. + const mediaSrc = media.source_url || media.url; + const alt = media.alt_text || media.alt || undefined; + const caption = media.caption?.raw || media.caption; + if ( caption && typeof caption === 'string' ) { + attributes.caption = caption; + } + if ( mediaType === 'image' ) { + attributes.url = mediaSrc; + attributes.alt = alt; + } else if ( [ 'video', 'audio' ].includes( mediaType ) ) { + attributes.src = mediaSrc; + } + const PreviewTag = mediaTypeTag[ mediaType ]; + const preview = ( + + ); + return [ createBlock( `core/${ mediaType }`, attributes ), preview ]; +} diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index c6d019c8ff9a74..37a09dd4488700 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -28,6 +28,7 @@ import BlockPatternsTabs, { BlockPatternsCategoryDialog, } from './block-patterns-tab'; import ReusableBlocksTab from './reusable-blocks-tab'; +import { MediaTab, MediaCategoryDialog, useMediaCategories } from './media-tab'; import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; import InserterTabs from './tabs'; @@ -54,6 +55,8 @@ function InserterMenu( const [ hoveredItem, setHoveredItem ] = useState( null ); const [ selectedPatternCategory, setSelectedPatternCategory ] = useState( null ); + const [ selectedMediaCategory, setSelectedMediaCategory ] = + useState( null ); const [ selectedTab, setSelectedTab ] = useState( null ); const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] = @@ -68,7 +71,6 @@ function InserterMenu( ( select ) => { const { __experimentalGetAllowedPatterns, getSettings } = select( blockEditorStore ); - return { showPatterns: !! __experimentalGetAllowedPatterns( destinationRootClientId @@ -80,6 +82,9 @@ function InserterMenu( [ destinationRootClientId ] ); + const mediaCategories = useMediaCategories( destinationRootClientId ); + const showMedia = !! mediaCategories.length; + const onInsert = useCallback( ( blocks, meta, shouldForceFocusBlock ) => { onInsertBlocks( blocks, meta, shouldForceFocusBlock ); @@ -170,16 +175,36 @@ function InserterMenu( [ destinationRootClientId, onInsert, onHover ] ); + const mediaTab = useMemo( + () => ( + + ), + [ + destinationRootClientId, + onInsert, + selectedMediaCategory, + setSelectedMediaCategory, + ] + ); + const getCurrentTab = useCallback( ( tab ) => { if ( tab.name === 'blocks' ) { return blocksTab; } else if ( tab.name === 'patterns' ) { return patternsTab; + } else if ( tab.name === 'reusable' ) { + return reusableBlocksTab; + } else if ( tab.name === 'media' ) { + return mediaTab; } - return reusableBlocksTab; }, - [ blocksTab, patternsTab, reusableBlocksTab ] + [ blocksTab, patternsTab, reusableBlocksTab, mediaTab ] ); const searchRef = useRef(); @@ -191,8 +216,10 @@ function InserterMenu( const showPatternPanel = selectedTab === 'patterns' && ! filterValue && selectedPatternCategory; - const showAsTabs = ! filterValue && ( showPatterns || hasReusableBlocks ); - + const showAsTabs = + ! filterValue && ( showPatterns || hasReusableBlocks || showMedia ); + const showMediaPanel = + selectedTab === 'media' && ! filterValue && selectedMediaCategory; return (
@@ -244,6 +272,13 @@ function InserterMenu(
) }
+ { showMediaPanel && ( + + ) } { showInserterHelpPanel && hoveredItem && ( ) } diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index ad5a2d6642b4a2..dc1036a5b5c260 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -480,3 +480,151 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__patterns-category-panel-title { font-size: calc(1.25 * 13px); } + +.block-editor-inserter__media-tabs-container { + height: 100%; + + nav { + height: 100%; + } + + .block-editor-inserter__media-library-button { + padding: $grid-unit-20; + justify-content: center; + margin-top: $grid-unit-20; + width: 100%; + } +} + +.block-editor-inserter__media-tabs { + display: flex; + flex-direction: column; + padding: $grid-unit-20; + overflow-y: auto; + height: 100%; + + // Push the listitem wrapping the "open media library" button to the bottom of the panel. + div[role="listitem"]:last-child { + margin-top: auto; + } + + &__media-category { + &.is-selected { + color: var(--wp-admin-theme-color); + position: relative; + + .components-flex-item { + filter: brightness(0.95); + } + + svg { + fill: var(--wp-admin-theme-color); + } + + &::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + border-radius: $radius-block-ui; + opacity: 0.04; + background: var(--wp-admin-theme-color); + } + } + } +} + +.block-editor-inserter__media-dialog { + background: $gray-100; + border-left: $border-width solid $gray-200; + border-right: $border-width solid $gray-200; + position: absolute; + padding: $grid-unit-20 $grid-unit-30; + top: 0; + left: 0; + height: 100%; + width: 100%; + overflow-y: auto; + scrollbar-gutter: stable both-edges; + + @include break-medium { + left: 100%; + display: block; + width: 300px; + } + + .block-editor-block-preview__container { + box-shadow: 0 15px 25px rgb(0 0 0 / 7%); + &:hover { + box-shadow: 0 0 0 2px $gray-900, 0 15px 25px rgb(0 0 0 / 7%); + } + } + + .block-editor-inserter__media-panel { + height: 100%; + + &-title { + font-size: calc(1.25 * 13px); + } + + &-spinner { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + &-search { + &.components-search-control { + input[type="search"].components-search-control__input { + background: $white; + } + } + } + } +} + +.block-editor-inserter__media-list { + margin-top: $grid-unit-20; + &__list-item { + cursor: pointer; + margin-bottom: $grid-unit-30; + + &.is-placeholder { + min-height: 100px; + } + + &[draggable="true"] .block-editor-block-preview__container { + cursor: grab; + } + } + + &__item { + height: 100%; + + &-preview { + display: flex; + align-items: center; + overflow: hidden; + border-radius: 4px; + + > * { + margin: 0 auto; + max-width: 100%; + } + } + + &:hover &-preview { + box-shadow: 0 0 0 2px var(--wp-admin-theme-color); + } + + &:focus &-preview { + box-shadow: inset 0 0 0 1px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + } +} diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index 24bfade8b2f4be..8a80e96cb78cc4 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -22,11 +22,17 @@ const reusableBlocksTab = { title: __( 'Reusable' ), icon: reusableBlockIcon, }; +const mediaTab = { + name: 'media', + /* translators: Media tab title in the block inserter. */ + title: __( 'Media' ), +}; function InserterTabs( { children, showPatterns = false, showReusableBlocks = false, + showMedia = false, onSelect, prioritizePatterns, } ) { @@ -39,10 +45,12 @@ function InserterTabs( { if ( ! prioritizePatterns && showPatterns ) { tempTabs.push( patternsTab ); } + if ( showMedia ) { + tempTabs.push( mediaTab ); + } if ( showReusableBlocks ) { tempTabs.push( reusableBlocksTab ); } - return tempTabs; }, [ prioritizePatterns, @@ -50,6 +58,7 @@ function InserterTabs( { showPatterns, patternsTab, showReusableBlocks, + showMedia, reusableBlocksTab, ] ); diff --git a/packages/compose/src/hooks/use-focus-outside/index.ts b/packages/compose/src/hooks/use-focus-outside/index.ts index cbf1d45d8c9e47..0d62318d38eca4 100644 --- a/packages/compose/src/hooks/use-focus-outside/index.ts +++ b/packages/compose/src/hooks/use-focus-outside/index.ts @@ -151,6 +151,23 @@ export default function useFocusOutside( return; } + // The usage of this attribute should be avoided. The only use case + // would be when we load modals that are not React components and + // therefore don't exist in the React tree. An example is opening + // the Media Library modal from another dialog. + // This attribute should contain a selector of the related target + // we want to ignore, because we still need to trigger the blur event + // on all other cases. + const ignoreForRelatedTarget = event.target.getAttribute( + 'data-unstable-ignore-focus-outside-for-relatedtarget' + ); + if ( + ignoreForRelatedTarget && + event.relatedTarget?.closest( ignoreForRelatedTarget ) + ) { + return; + } + blurCheckTimeoutId.current = setTimeout( () => { // If document is not focused then focus should remain // inside the wrapped component and therefore we cancel diff --git a/packages/core-data/src/fetch/fetch-media.js b/packages/core-data/src/fetch/fetch-media.js new file mode 100644 index 00000000000000..c7a3a429a8627b --- /dev/null +++ b/packages/core-data/src/fetch/fetch-media.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { resolveSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { STORE_NAME as coreStore } from '../name'; + +export default async function fetchMedia( settings = {} ) { + return resolveSelect( coreStore ).getMediaItems( settings ); +} diff --git a/packages/core-data/src/fetch/index.js b/packages/core-data/src/fetch/index.js index 8d4d28e3b0db82..2f765825f3e702 100644 --- a/packages/core-data/src/fetch/index.js +++ b/packages/core-data/src/fetch/index.js @@ -1,2 +1,3 @@ export { default as __experimentalFetchLinkSuggestions } from './__experimental-fetch-link-suggestions'; export { default as __experimentalFetchUrlData } from './__experimental-fetch-url-data'; +export { default as __experimentalFetchMedia } from './fetch-media'; diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 0f42214b454421..f990ee7f15f697 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -8,7 +8,11 @@ import classnames from 'classnames'; */ import { useSelect, useDispatch } from '@wordpress/data'; import { useCallback, useMemo, useRef, Fragment } from '@wordpress/element'; -import { useEntityBlockEditor, store as coreStore } from '@wordpress/core-data'; +import { + useEntityBlockEditor, + __experimentalFetchMedia as fetchMedia, + store as coreStore, +} from '@wordpress/core-data'; import { BlockList, BlockEditorProvider, @@ -131,6 +135,7 @@ export default function BlockEditor( { setIsInserterOpen } ) { return { ...restStoredSettings, + __unstableFetchMedia: fetchMedia, __experimentalBlockPatterns: blockPatterns, __experimentalBlockPatternCategories: blockPatternCategories, }; diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 0b69dcc66eb81c..69b4f9b42cfc63 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -7,6 +7,7 @@ import { store as coreStore, __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, + __experimentalFetchMedia as fetchMedia, } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; @@ -182,6 +183,9 @@ function useBlockEditorSettings( settings, hasTemplate ) { __experimentalBlockPatternCategories: blockPatternCategories, __experimentalFetchLinkSuggestions: ( search, searchOptions ) => fetchLinkSuggestions( search, searchOptions, settings ), + // TODO: We should find a proper way to consolidate similar cases + // like reusable blocks, fetch entities, etc. + __unstableFetchMedia: fetchMedia, __experimentalFetchRichUrlData: fetchUrlData, __experimentalCanUserUseUnfilteredHTML: canUseUnfilteredHTML, __experimentalUndo: undo, diff --git a/test/e2e/specs/editor/various/inserting-blocks.spec.js b/test/e2e/specs/editor/various/inserting-blocks.spec.js index cc75245d48bac3..f45a9116508233 100644 --- a/test/e2e/specs/editor/various/inserting-blocks.spec.js +++ b/test/e2e/specs/editor/various/inserting-blocks.spec.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +const path = require( 'path' ); + /** * WordPress dependencies */ @@ -286,6 +291,50 @@ test.describe( 'Inserting blocks (@firefox, @webkit)', () => { } ); } ); +test.describe( 'insert media from inserter', () => { + let uploadedMedia; + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.deleteAllMedia(); + uploadedMedia = await requestUtils.uploadMedia( + path.resolve( + process.cwd(), + 'test/e2e/assets/10x10_e2e_test_image_z9T8jK.png' + ) + ); + } ); + test.afterAll( async ( { requestUtils } ) => { + Promise.all( [ + requestUtils.deleteAllMedia(), + requestUtils.deleteAllPosts(), + ] ); + } ); + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + test( 'insert media from the global inserter', async ( { + page, + editor, + } ) => { + await page.click( + 'role=region[name="Editor top bar"i] >> role=button[name="Toggle block inserter"i]' + ); + await page.click( + 'role=region[name="Block Library"i] >> role=tab[name="Media"i]' + ); + await page.click( + '[aria-label="Media categories"i] >> role=button[name="Images"i]' + ); + await page.click( + `role=listbox[name="Media List"i] >> role=option[name="${ uploadedMedia.title.raw }"]` + ); + await expect.poll( editor.getEditedPostContent ).toBe( + ` +
${ uploadedMedia.alt_text }
+` + ); + } ); +} ); + class InsertingBlocksUtils { constructor( { page, editor } ) { this.page = page; From 362a6c40d8c66cef6ef1f912602993fb7aa86ac5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Nov 2022 13:37:25 +0000 Subject: [PATCH 087/384] Limit back to Nav block and always skip to Nav block (#46037) --- .../block-editor/src/components/block-card/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 622d13f2c278dc..dab832861ae661 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -30,15 +30,16 @@ function BlockCard( { title, icon, description, blockType, className } ) { const isOffCanvasNavigationEditorEnabled = window?.__experimentalEnableOffCanvasNavigationEditor === true; - const { parentBlockClientId } = useSelect( ( select ) => { - const { getSelectedBlockClientId, getBlockParents } = + const { parentNavBlockClientId } = useSelect( ( select ) => { + const { getSelectedBlockClientId, getBlockParentsByBlockName } = select( blockEditorStore ); const _selectedBlockClientId = getSelectedBlockClientId(); return { - parentBlockClientId: getBlockParents( + parentNavBlockClientId: getBlockParentsByBlockName( _selectedBlockClientId, + 'core/navigation', true )[ 0 ], }; @@ -48,10 +49,10 @@ function BlockCard( { title, icon, description, blockType, className } ) { return (
- { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( + { isOffCanvasNavigationEditorEnabled && parentNavBlockClientId && ( + + + + + ) : ( + + { label } + + ) } + { hasChildren && ( + <> + { ! context.openSubmenusOnClick && + context.showSubmenuIcon && ( + + ) } +
    + +
+ + ) } + + ); +} diff --git a/packages/block-library/src/page-list-item/index.js b/packages/block-library/src/page-list-item/index.js new file mode 100644 index 00000000000000..18b7fe2e40e9ea --- /dev/null +++ b/packages/block-library/src/page-list-item/index.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { pages as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import metadata from './block.json'; +import edit from './edit.js'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + __experimentalLabel: ( { label } ) => label, + icon, + example: {}, + edit, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/page-list-item/init.js b/packages/block-library/src/page-list-item/init.js new file mode 100644 index 00000000000000..79f0492c2cb2f8 --- /dev/null +++ b/packages/block-library/src/page-list-item/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index ac7ed6162cf975..d9e541305b6976 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -10,6 +10,7 @@ import { InspectorControls, BlockControls, useBlockProps, + useInnerBlocksProps, getColorClassName, } from '@wordpress/block-editor'; import { @@ -20,15 +21,13 @@ import { ComboboxControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState, memo } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore, useEntityRecords } from '@wordpress/core-data'; +import { useMemo, useState } from '@wordpress/element'; +import { useEntityRecords } from '@wordpress/core-data'; /** * Internal dependencies */ import ConvertToLinksModal from './convert-to-links-modal'; -import { ItemSubmenuIcon } from '../navigation-link/icons'; // We only show the edit option when page count is <= MAX_PAGE_COUNT // Performance of Navigation Links is not good past this value. @@ -65,6 +64,39 @@ export default function PageListEdit( { style: { ...context.style?.color }, } ); + const makeBlockTemplate = ( parentId = 0 ) => { + const pages = pagesByParentId.get( parentId ); + + if ( ! pages?.length ) { + return []; + } + + return pages.reduce( ( template, page ) => { + const hasChildren = pagesByParentId.has( page.id ); + const pageProps = { + id: page.id, + label: page.title?.rendered, + title: page.title?.rendered, + link: page.url, + hasChildren, + }; + let item = null; + const children = makeBlockTemplate( page.id ); + item = [ 'core/page-list-item', pageProps, children ]; + + template.push( item ); + + return template; + }, [] ); + }; + + const pagesTemplate = useMemo( makeBlockTemplate, [ pagesByParentId ] ); + + const innerBlocksProps = useInnerBlocksProps( blockProps, { + template: pagesTemplate, + templateLock: 'all', + } ); + const getBlockContent = () => { if ( ! hasResolvedPages ) { return ( @@ -95,15 +127,7 @@ export default function PageListEdit( { } if ( totalPages > 0 ) { - return ( -
    - -
- ); + return
    ; } }; @@ -155,21 +179,6 @@ export default function PageListEdit( { ); } -function useFrontPageId() { - return useSelect( ( select ) => { - const canReadSettings = select( coreStore ).canUser( - 'read', - 'settings' - ); - if ( ! canReadSettings ) { - return undefined; - } - - const site = select( coreStore ).getEntityRecord( 'root', 'site' ); - return site?.show_on_front === 'page' && site?.page_on_front; - }, [] ); -} - function useGetPages() { const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', @@ -221,92 +230,3 @@ function usePageData( pageId = 0 ) { }; }, [ pageId, pages, hasResolvedPages ] ); } - -const PageItems = memo( function PageItems( { - context, - pagesByParentId, - parentId = 0, - depth = 0, -} ) { - const parentPage = usePageData( parentId ); - const pages = pagesByParentId.get( parentId ) - ? pagesByParentId.get( parentId ) - : [ parentPage ]; - const frontPageId = useFrontPageId(); - - if ( ! pages?.length ) { - return []; - } - - return pages.map( ( page ) => { - const hasChildren = pagesByParentId.has( page.id ); - const isNavigationChild = 'showSubmenuIcon' in context; - return ( -
  • - { hasChildren && context.openSubmenusOnClick ? ( - <> - - - - - - ) : ( - - { page.title?.rendered } - - ) } - { hasChildren && ( - <> - { ! context.openSubmenusOnClick && - context.showSubmenuIcon && ( - - ) } -
      - -
    - - ) } -
  • - ); - } ); -} ); diff --git a/test/integration/fixtures/blocks/core__page-list-item.html b/test/integration/fixtures/blocks/core__page-list-item.html new file mode 100644 index 00000000000000..2b1b33d71231cf --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.html @@ -0,0 +1 @@ + diff --git a/test/integration/fixtures/blocks/core__page-list-item.json b/test/integration/fixtures/blocks/core__page-list-item.json new file mode 100644 index 00000000000000..5e6f09eb971eb7 --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.json @@ -0,0 +1,11 @@ +[ + { + "name": "core/page-list-item", + "isValid": true, + "attributes": { + "label": "Page", + "link": "https://example.com" + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__page-list-item.parsed.json b/test/integration/fixtures/blocks/core__page-list-item.parsed.json new file mode 100644 index 00000000000000..e93cff97dd385e --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.parsed.json @@ -0,0 +1,14 @@ +[ + { + "blockName": "core/page-list-item", + "attrs": { + "id": "1", + "label": "Page", + "link": "https://example.com", + "hasChildren": "false" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/fixtures/blocks/core__page-list-item.serialized.html b/test/integration/fixtures/blocks/core__page-list-item.serialized.html new file mode 100644 index 00000000000000..d61659fe1899a9 --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.serialized.html @@ -0,0 +1 @@ + From 0f6bfbb461bfe44bfec474c7b66547485451f43f Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Fri, 25 Nov 2022 17:45:30 +0000 Subject: [PATCH 090/384] Bump plugin version to 14.6.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 3c45ea942c9171..0082600db8620f 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.9 * Requires PHP: 5.6 - * Version: 14.6.0 + * Version: 14.6.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index daeab8f712fc12..31d7463938053e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.6.0", + "version": "14.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 759a069c6e9669..7681db5a975514 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.6.0", + "version": "14.6.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 0a1b333ae1c3a66f160965fd8000e150aebd87dd Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Fri, 25 Nov 2022 18:05:16 +0000 Subject: [PATCH 091/384] Update Changelog for 14.6.1 --- changelog.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/changelog.txt b/changelog.txt index e0ff8b9afe40d5..3a8c82ca84c2c8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,26 @@ == Changelog == += 14.6.1 = + +## Changelog + +### Fixes + +#### Global Styles +- Fix the `upgrader_process_complete` hook for `wp_theme_has_theme_json`. ([45881](https://github.com/WordPress/gutenberg/pull/45881)) +- Update `wp_theme_has_theme_json` to use `WP_Object_Cache`. ([45543](https://github.com/WordPress/gutenberg/pull/45543)) + +### Bug Fixes +- Tag Processor: Prevent bugs from pre-PHP8 strspn/strcspn behavior. ([45822](https://github.com/WordPress/gutenberg/pull/45822)) + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal @dmsnell + + + = 14.6.0 = ## Changelog From 40aa5e62ed7f517c1d45548227d4e1845aae850a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 26 Nov 2022 00:31:49 +0100 Subject: [PATCH 092/384] `NumberControl`: refactor styles/tests/stories to TypeScript, replace `fireEvent` with `user-event` (#45990) * Rename files * Fix Storybook example types * Rename and type styles * Avoid unnecessary wrapping of `BaseNumberControl` * Type `getInput` * Fix more minor type errors * Use `toBeInTheDocument` when possible * Replace `fireEvent` with `user-event` * Use `toHaveValue` instead of accessing `input.value` directly` * Update failing tests to make them pass using `user-event` * Alternative blur method * CHANGELOG * Fix wrong comment * Adding missing Storybook config * `toBeInTheDocument()` => `toBeVisible()` * Remove `getInput()`, replace with inline query * Remove redundant `={true}` * Refine spinbutton expected onChange argument * Remove duplicate test * Improve test wording --- packages/components/CHANGELOG.md | 4 + .../stories/{index.js => index.tsx} | 31 +- ...rol-styles.js => number-control-styles.ts} | 8 +- .../src/number-control/test/index.js | 478 -------------- .../src/number-control/test/index.tsx | 603 ++++++++++++++++++ 5 files changed, 636 insertions(+), 488 deletions(-) rename packages/components/src/number-control/stories/{index.js => index.tsx} (56%) rename packages/components/src/number-control/styles/{number-control-styles.js => number-control-styles.ts} (81%) delete mode 100644 packages/components/src/number-control/test/index.js create mode 100644 packages/components/src/number-control/test/index.tsx diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c57f3b01d58256..f46afbb1d18985 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,10 @@ - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +### Internal + +- NumberControl: refactor styles/tests/stories to TypeScript, replace fireEvent with user-event ([#45990](https://github.com/WordPress/gutenberg/pull/45990)). + ## 22.1.0 (2022-11-16) ### Enhancements diff --git a/packages/components/src/number-control/stories/index.js b/packages/components/src/number-control/stories/index.tsx similarity index 56% rename from packages/components/src/number-control/stories/index.js rename to packages/components/src/number-control/stories/index.tsx index 4daff7e34b2baa..8dce45db26d5da 100644 --- a/packages/components/src/number-control/stories/index.js +++ b/packages/components/src/number-control/stories/index.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + /** * WordPress dependencies */ @@ -6,9 +11,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import NumberControl from '../'; +import NumberControl from '..'; -export default { +const meta: ComponentMeta< typeof NumberControl > = { title: 'Components (Experimental)/NumberControl', component: NumberControl, argTypes: { @@ -19,10 +24,19 @@ export default { type: { control: { type: 'text' } }, value: { control: null }, }, + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, }; -function Template( { onChange, ...props } ) { - const [ value, setValue ] = useState( '0' ); +export default meta; + +const Template: ComponentStory< typeof NumberControl > = ( { + onChange, + ...props +} ) => { + const [ value, setValue ] = useState< string | undefined >( '0' ); const [ isValidValue, setIsValidValue ] = useState( true ); return ( @@ -32,14 +46,17 @@ function Template( { onChange, ...props } ) { value={ value } onChange={ ( v, extra ) => { setValue( v ); - setIsValidValue( extra.event.target.validity.valid ); - onChange( v, extra ); + setIsValidValue( + ( extra.event.target as HTMLInputElement ).validity + .valid + ); + onChange?.( v, extra ); } } />

    Is valid? { isValidValue ? 'Yes' : 'No' }

    ); -} +}; export const Default = Template.bind( {} ); Default.args = { diff --git a/packages/components/src/number-control/styles/number-control-styles.js b/packages/components/src/number-control/styles/number-control-styles.ts similarity index 81% rename from packages/components/src/number-control/styles/number-control-styles.js rename to packages/components/src/number-control/styles/number-control-styles.ts index 4f0bcb91bb8dda..dfc6171cf411b4 100644 --- a/packages/components/src/number-control/styles/number-control-styles.js +++ b/packages/components/src/number-control/styles/number-control-styles.ts @@ -1,4 +1,3 @@ -// @ts-nocheck /** * External dependencies */ @@ -12,8 +11,9 @@ import InputControl from '../../input-control'; import { COLORS } from '../../utils'; import Button from '../../button'; import { space } from '../../ui/utils/space'; +import type { NumberControlProps } from '../types'; -const htmlArrowStyles = ( { hideHTMLArrows } ) => { +const htmlArrowStyles = ( { hideHTMLArrows }: { hideHTMLArrows: boolean } ) => { if ( ! hideHTMLArrows ) { return ``; } @@ -35,7 +35,9 @@ export const Input = styled( InputControl )` ${ htmlArrowStyles }; `; -const spinButtonSizeStyles = ( { size } ) => { +const spinButtonSizeStyles = ( { + size, +}: Pick< NumberControlProps, 'size' > ) => { if ( size !== 'small' ) { return ``; } diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js deleted file mode 100644 index b1a57232ef2754..00000000000000 --- a/packages/components/src/number-control/test/index.js +++ /dev/null @@ -1,478 +0,0 @@ -/** - * External dependencies - */ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; -import { UP, DOWN, ENTER } from '@wordpress/keycodes'; - -/** - * Internal dependencies - */ -import BaseNumberControl from '../'; - -const getInput = () => screen.getByTestId( 'input' ); - -const fireKeyDown = ( data ) => - fireEvent.keyDown( document.activeElement || document.body, data ); - -const NumberControl = ( props ) => ( - -); - -function StatefulNumberControl( props ) { - const [ value, setValue ] = useState( props.value ); - const handleOnChange = ( v ) => setValue( v ); - - return ( - - ); -} - -describe( 'NumberControl', () => { - describe( 'Basic rendering', () => { - it( 'should render', () => { - render( ); - expect( getInput() ).not.toBeNull(); - } ); - - it( 'should render custom className', () => { - render( ); - expect( getInput() ).toBeTruthy(); - } ); - } ); - - describe( 'onChange handling', () => { - it( 'should provide onChange callback with number value', () => { - const spy = jest.fn(); - - render( - spy( v ) } /> - ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 10 } } ); - - expect( spy ).toHaveBeenCalledWith( '10' ); - } ); - - it( 'should call onChange callback when value is clamped on blur', async () => { - const spy = jest.fn(); - render( - spy( v ) } - /> - ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 1 } } ); - - // Before blurring, the value is still un-clamped - expect( input.value ).toBe( '1' ); - - input.blur(); - - // After blur, value is clamped - expect( input.value ).toBe( '4' ); - - // After the blur, the `onChange` callback fires asynchronously. - await waitFor( () => { - expect( spy ).toHaveBeenCalledTimes( 2 ); - } ); - - expect( spy ).toHaveBeenNthCalledWith( 1, '1' ); - expect( spy ).toHaveBeenNthCalledWith( 2, 4 ); - } ); - - it( 'should call onChange callback when value is not valid', () => { - const spy = jest.fn(); - render( - - spy( v, extra.event.target.validity.valid ) - } - /> - ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 14 } } ); - - expect( input.value ).toBe( '14' ); - - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '10' ); - - expect( spy ).toHaveBeenCalledTimes( 2 ); - - // First call: invalid, unclamped value - expect( spy ).toHaveBeenNthCalledWith( 1, '14', false ); - // Second call: valid, clamped value - expect( spy ).toHaveBeenNthCalledWith( 2, 10, true ); - } ); - } ); - - describe( 'Validation', () => { - it( 'should clamp value within range on ENTER keypress', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: -100 } } ); - fireKeyDown( { keyCode: ENTER } ); - - /** - * This is zero because the value has been adjusted to - * respect the min/max range of the input. - */ - - expect( input.value ).toBe( '0' ); - } ); - - it( 'should clamp value within range on blur', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 41 } } ); - - // Before blurring, the value is still un-clamped - expect( input.value ).toBe( '41' ); - - input.blur(); - - // After blur, value is clamped - expect( input.value ).toBe( '10' ); - } ); - - it( 'should parse to number value on ENTER keypress when required', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '10 abc' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '0' ); - } ); - - it( 'should parse to empty string on ENTER keypress when not required', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '10 abc' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '' ); - } ); - - it( 'should accept empty string on ENTER keypress for optional field', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '' ); - } ); - - it( 'should not enforce numerical value for empty string when required is omitted', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '' ); - } ); - - it( 'should enforce numerical value for empty string when required', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '0' ); - } ); - } ); - - describe( 'Key UP interactions', () => { - it( 'should fire onKeyDown callback', () => { - const spy = jest.fn(); - - render( ); - - getInput().focus(); - fireKeyDown( { keyCode: UP } ); - - expect( spy ).toHaveBeenCalled(); - } ); - - it( 'should increment by step on key UP press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP } ); - - expect( input.value ).toBe( '6' ); - } ); - - it( 'should increment from a negative value', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP } ); - - expect( input.value ).toBe( '-4' ); - } ); - - it( 'should increment while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should increment by shiftStep on key UP + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '20' ); - } ); - - it( 'should increment by shiftStep while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should increment by custom shiftStep on key UP + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '100' ); - } ); - - it( 'should increment but be limited by max on shiftStep', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '99' ); - } ); - - it( 'should not increment by shiftStep if disabled', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '6' ); - } ); - } ); - - describe( 'Key DOWN interactions', () => { - it( 'should fire onKeyDown callback', () => { - const spy = jest.fn(); - render( ); - - getInput().focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( spy ).toHaveBeenCalled(); - } ); - - it( 'should decrement by step on key DOWN press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( input.value ).toBe( '4' ); - } ); - - it( 'should decrement from a negative value', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( input.value ).toBe( '-6' ); - } ); - - it( 'should decrement while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should decrement by shiftStep on key DOWN + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '0' ); - } ); - - it( 'should decrement by shiftStep while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should decrement by custom shiftStep on key DOWN + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '-100' ); - } ); - - it( 'should decrement but be limited by min on shiftStep', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '4' ); - } ); - - it( 'should not decrement by shiftStep if disabled', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '4' ); - } ); - } ); - - describe( 'custom spin buttons', () => { - test.each( [ undefined, 'none', 'native' ] )( - 'should not appear when spinControls = %s', - ( spinControls ) => { - render( ); - expect( - screen.queryByLabelText( 'Increment' ) - ).not.toBeInTheDocument(); - expect( - screen.queryByLabelText( 'Decrement' ) - ).not.toBeInTheDocument(); - } - ); - - test.each( [ - [ 'up', '1', {} ], - [ 'up', '2', { value: '1' } ], - [ 'up', '12', { value: '10', step: '2' } ], - [ 'up', '10', { value: '10', max: '10' } ], - [ 'down', '-1', {} ], - [ 'down', '1', { value: '2' } ], - [ 'down', '10', { value: '12', step: '2' } ], - [ 'down', '10', { value: '10', min: '10' } ], - ] )( - 'should spin %s to %s when props = %o', - async ( direction, expectedValue, props ) => { - const user = userEvent.setup( { - advanceTimers: jest.advanceTimersByTime, - } ); - const onChange = jest.fn(); - render( - - ); - await user.click( - screen.getByLabelText( - direction === 'up' ? 'Increment' : 'Decrement' - ) - ); - expect( onChange ).toHaveBeenCalledWith( expectedValue, { - event: expect.anything(), - } ); - } - ); - } ); -} ); diff --git a/packages/components/src/number-control/test/index.tsx b/packages/components/src/number-control/test/index.tsx new file mode 100644 index 00000000000000..2eeef4c996ae63 --- /dev/null +++ b/packages/components/src/number-control/test/index.tsx @@ -0,0 +1,603 @@ +/** + * External dependencies + */ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import NumberControl from '..'; +import type { NumberControlProps } from '../types'; + +function StatefulNumberControl( props: NumberControlProps ) { + const [ value, setValue ] = useState( props.value ); + const handleOnChange = ( v: string | undefined ) => setValue( v ); + + return ( + + ); +} + +describe( 'NumberControl', () => { + describe( 'Basic rendering', () => { + it( 'should render', () => { + render( ); + expect( screen.getByRole( 'spinbutton' ) ).toBeVisible(); + } ); + + it( 'should render custom className', () => { + render( ); + expect( screen.getByRole( 'spinbutton' ) ).toBeVisible(); + } ); + } ); + + describe( 'onChange handling', () => { + it( 'should provide onChange callback with number value', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const spy = jest.fn(); + + render( + spy( v ) } /> + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, '10' ); + + expect( spy ).toHaveBeenCalledWith( '10' ); + } ); + + it( 'should call onChange callback when value is clamped on blur', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChangeSpy = jest.fn(); + + render( + + onChangeSpy( + v, + ( extra.event.target as HTMLInputElement ).validity + .valid + ) + } + /> + ); + + const input = screen.getByRole( 'spinbutton' ); + + await user.clear( input ); + await user.type( input, '1' ); + + // Before blurring, the value is still un-clamped + expect( input ).toHaveValue( 1 ); + + // Blur the input + await user.keyboard( '[Tab]' ); + + // After blur, value is clamped + expect( input ).toHaveValue( 4 ); + + // After the blur, the `onChange` callback fires asynchronously. + await waitFor( () => { + expect( onChangeSpy ).toHaveBeenCalledTimes( 3 ); + } ); + + // First call: clear the input + expect( onChangeSpy ).toHaveBeenNthCalledWith( 1, '', true ); + // Second call: type '1' + expect( onChangeSpy ).toHaveBeenNthCalledWith( 2, '1', false ); + // Third call: clamp value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 3, 4, true ); + } ); + + it( 'should call onChange callback when value is not valid', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChangeSpy = jest.fn(); + + render( + + onChangeSpy( + v, + ( extra.event.target as HTMLInputElement ).validity + .valid + ) + } + /> + ); + + const input = screen.getByRole( 'spinbutton' ); + + await user.clear( input ); + await user.type( input, '14' ); + + expect( input ).toHaveValue( 14 ); + + await user.keyboard( '[Enter]' ); + + expect( input ).toHaveValue( 10 ); + + expect( onChangeSpy ).toHaveBeenCalledTimes( 4 ); + + // First call: clear value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 1, '', true ); + // Second call: valid, unclamped value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 2, '1', true ); + // Third call: invalid, unclamped value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 3, '14', false ); + // Fourth call: valid, clamped value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 4, 10, true ); + } ); + } ); + + describe( 'Validation', () => { + it( 'should clamp value within range on ENTER keypress', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + + await user.clear( input ); + await user.type( input, '-100' ); + await user.keyboard( '[Enter]' ); + + /** + * This is zero because the value has been adjusted to + * respect the min/max range of the input. + */ + expect( input ).toHaveValue( 0 ); + } ); + + it( 'should clamp value within range on blur', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, '41' ); + + // Before blurring, the value is still un-clamped + expect( input ).toHaveValue( 41 ); + + // Blur the input + await user.click( document.body ); + + // After blur, value is clamped + expect( input ).toHaveValue( 10 ); + } ); + + it( 'should parse non-numeric values to a number on ENTER keypress when required', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, 'abc' ); + await user.keyboard( '[Enter]' ); + + expect( input ).toHaveValue( 0 ); + } ); + + it( 'should parse non-numeric values to empty string on ENTER keypress when not required', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, 'abc' ); + await user.keyboard( '[Enter]' ); + + // Note: the `input` field may still visually show the invalid input, + // while internally parsing the value as an empty string. + // + // React Testing Library associates `null` to empty string types for + // numeric `input` elements + // (see https://github.com/testing-library/jest-dom/blob/v5.16.5/src/utils.js#L191-L192) + expect( input ).toHaveValue( null ); + } ); + + it( 'should not enforce numerical value for empty string when required is omitted', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.keyboard( '[Enter]' ); + + // React Testing Library associates `null` to empty string types for + // numeric `input` elements + // (see https://github.com/testing-library/jest-dom/blob/v5.16.5/src/utils.js#L191-L192) + expect( input ).toHaveValue( null ); + } ); + + it( 'should enforce numerical value for empty string when required', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.keyboard( '[Enter]' ); + + expect( input ).toHaveValue( 0 ); + } ); + } ); + + describe( 'Key UP interactions', () => { + it( 'should fire onKeyDown callback', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + const spy = jest.fn(); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( spy ).toHaveBeenCalled(); + } ); + + it( 'should increment by step on key UP press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( input ).toHaveValue( 6 ); + } ); + + it( 'should increment from a negative value', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( input ).toHaveValue( -4 ); + } ); + + it( 'should increment while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should increment by shiftStep on key UP + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 20 ); + } ); + + it( 'should increment by shiftStep while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should increment by custom shiftStep on key UP + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 100 ); + } ); + + it( 'should increment but be limited by max on shiftStep', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 99 ); + } ); + + it( 'should not increment by shiftStep if disabled', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 6 ); + } ); + } ); + + describe( 'Key DOWN interactions', () => { + it( 'should fire onKeyDown callback', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const spy = jest.fn(); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( spy ).toHaveBeenCalled(); + } ); + + it( 'should decrement by step on key DOWN press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( input ).toHaveValue( 4 ); + } ); + + it( 'should decrement from a negative value', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( input ).toHaveValue( -6 ); + } ); + + it( 'should decrement while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should decrement by shiftStep on key DOWN + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 0 ); + } ); + + it( 'should decrement by shiftStep while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should decrement by custom shiftStep on key DOWN + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( -100 ); + } ); + + it( 'should decrement but be limited by min on shiftStep', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 4 ); + } ); + + it( 'should not decrement by shiftStep if disabled', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 4 ); + } ); + } ); + + describe( 'custom spin buttons', () => { + test.each( [ + undefined, + 'none', + 'native', + ] as NumberControlProps[ 'spinControls' ][] )( + 'should not appear when spinControls = %s', + ( spinControls ) => { + render( ); + expect( + screen.queryByLabelText( 'Increment' ) + ).not.toBeInTheDocument(); + expect( + screen.queryByLabelText( 'Decrement' ) + ).not.toBeInTheDocument(); + } + ); + + test.each( [ + [ 'up', '1', {} ], + [ 'up', '2', { value: '1' } ], + [ 'up', '12', { value: '10', step: '2' } ], + [ 'up', '10', { value: '10', max: 10 } ], + [ 'down', '-1', {} ], + [ 'down', '1', { value: '2' } ], + [ 'down', '10', { value: '12', step: '2' } ], + [ 'down', '10', { value: '10', min: 10 } ], + ] )( + 'should spin %s to %s when props = %o', + async ( direction, expectedValue, props ) => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByLabelText( + direction === 'up' ? 'Increment' : 'Decrement' + ) + ); + expect( onChange ).toHaveBeenCalledWith( expectedValue, { + event: expect.objectContaining( { + target: expect.any( HTMLInputElement ), + type: expect.any( String ), + } ), + } ); + } + ); + } ); +} ); From 5292d8051ce923917b4842c2f5555cfe07e5a62d Mon Sep 17 00:00:00 2001 From: Marcelo Serpa <81248+fullofcaffeine@users.noreply.github.com> Date: Sat, 26 Nov 2022 01:11:46 -0600 Subject: [PATCH 093/384] (release.md) Document the special case of shipping point releases when a new release branch already exists (#46083) --- docs/contributors/code/release.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 4ce35e189fd700..f463cb319f6657 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -150,6 +150,8 @@ The method for point releases is nearly identical to the main Plugin release pro The point release should only contain the _specific commits_ required. To do this you should checkout the previous _minor_ stable (i.e. non-RC) release branch (e.g. `release/12.5`) locally and then cherry pick any commits that you require into that branch. +_IMPORTANT:_ If an RC already exists for a new version, you _need_ to cherry-pick the same commits in the respective release branch, as they will not be included automatically. E.g.: If you're about to release a new point-release for 12.5 and just cherry-picked into `release/12.5`, but 12.6.0-rc.1 is already out, then you need to cherry-pick the same commits into the `release/12.6` branch, or they won't be included in subsequent releases for 12.6! + The cherry-picking process can be automated with the [`npm run cherry-pick` script](/docs/contributors/code/auto-cherry-picking.md). You must also ensure that all PRs being included are assigned to the Github Milestone on which the point release is based. Bear in mind, that when PRs are _merged_ they are automatically assigned a milestone for the next _stable_ release. Therefore you will need to go back through each PR in Github and re-assign the Milestone. From 0b1998cf997d866bc1eca182fedb1abb42e337fd Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 26 Nov 2022 12:45:27 +0100 Subject: [PATCH 094/384] `ColorPalette`: show `Clear` button even when `colors` array is empty (#46001) * Render `CircularOptionPicker` when `colors` is an empty array * Fix Storybook errors when `colors` array is empty * Move `onChange` mock inside each test * Rename `test` to `it` * Extract color constants outside `describe` scope, rename variables, use less US-centric colors * Add test to avoid regressions on `Clear` button not showing when `colors` is an empty array * Split clear button test into two separate tests * CHANGELOG * Remove initial color logic in Storybook example --- packages/components/CHANGELOG.md | 4 + .../components/src/color-palette/index.tsx | 4 - .../src/color-palette/stories/index.tsx | 6 +- .../test/__snapshots__/index.tsx.snap | 8 +- .../src/color-palette/test/index.tsx | 104 ++++++++++++------ 5 files changed, 78 insertions(+), 48 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index f46afbb1d18985..22a08c2f0dd08c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fix + +- `ColorPalette`: show "Clear" button even when colors array is empty ([#46001](https://github.com/WordPress/gutenberg/pull/46001)). + ### Enhancements - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx index 4c36a8e56df83c..5146e3c8581988 100644 --- a/packages/components/src/color-palette/index.tsx +++ b/packages/components/src/color-palette/index.tsx @@ -84,10 +84,6 @@ function SinglePalette( { } ); }, [ colors, value, onChange, clearColor ] ); - if ( colors.length === 0 ) { - return null; - } - return ( = { title: 'Components/ColorPalette', @@ -43,10 +42,7 @@ const Template: ComponentStory< typeof ColorPalette > = ( { onChange, ...args } ) => { - const firstColor = - ( args.colors as ColorObject[] )[ 0 ].color || - ( args.colors as PaletteObject[] )[ 0 ].colors[ 0 ].color; - const [ color, setColor ] = useState< string | undefined >( firstColor ); + const [ color, setColor ] = useState< string | undefined >(); return ( diff --git a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap index 573ff940ebf0db..d4ea4015c639fa 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap @@ -63,10 +63,10 @@ exports[`ColorPalette should allow disabling custom color picker 1`] = ` class="components-circular-option-picker__option-wrapper" >
    @@ -235,10 +235,10 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` class="components-circular-option-picker__option-wrapper" >
    diff --git a/packages/components/src/color-palette/test/index.tsx b/packages/components/src/color-palette/test/index.tsx index ce81b1241a5f4f..2cd95174cb4ed9 100644 --- a/packages/components/src/color-palette/test/index.tsx +++ b/packages/components/src/color-palette/test/index.tsx @@ -9,24 +9,21 @@ import userEvent from '@testing-library/user-event'; */ import ColorPalette from '..'; +const EXAMPLE_COLORS = [ + { name: 'red', color: '#f00' }, + { name: 'green', color: '#0f0' }, + { name: 'blue', color: '#00f' }, +]; +const INITIAL_COLOR = EXAMPLE_COLORS[ 0 ].color; + describe( 'ColorPalette', () => { - const colors = [ - { name: 'red', color: '#f00' }, - { name: 'white', color: '#fff' }, - { name: 'blue', color: '#00f' }, - ]; - const currentColor = '#f00'; - const onChange = jest.fn(); - - beforeEach( () => { - onChange.mockClear(); - } ); + it( 'should render a dynamic toolbar of colors', () => { + const onChange = jest.fn(); - test( 'should render a dynamic toolbar of colors', () => { const { container } = render( ); @@ -34,11 +31,13 @@ describe( 'ColorPalette', () => { expect( container ).toMatchSnapshot(); } ); - test( 'should render three color button options', () => { + it( 'should render three color button options', () => { + const onChange = jest.fn(); + render( ); @@ -48,15 +47,16 @@ describe( 'ColorPalette', () => { ).toHaveLength( 3 ); } ); - test( 'should call onClick on an active button with undefined', async () => { + it( 'should call onClick on an active button with undefined', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); @@ -69,19 +69,22 @@ describe( 'ColorPalette', () => { expect( onChange ).toHaveBeenCalledWith( undefined ); } ); - test( 'should call onClick on an inactive button', async () => { + it( 'should call onClick on an inactive button', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); + // Click the first unpressed button + // (i.e. a button representing a color that is not the current color) await user.click( screen.getAllByRole( 'button', { name: /^Color:/, @@ -89,19 +92,21 @@ describe( 'ColorPalette', () => { } )[ 0 ] ); + // Expect the green color to have been selected expect( onChange ).toHaveBeenCalledTimes( 1 ); - expect( onChange ).toHaveBeenCalledWith( '#fff', 1 ); + expect( onChange ).toHaveBeenCalledWith( EXAMPLE_COLORS[ 1 ].color, 1 ); } ); - test( 'should call onClick with undefined, when the clearButton onClick is triggered', async () => { + it( 'should call onClick with undefined, when the clearButton onClick is triggered', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); @@ -112,12 +117,14 @@ describe( 'ColorPalette', () => { expect( onChange ).toHaveBeenCalledWith( undefined ); } ); - test( 'should allow disabling custom color picker', () => { + it( 'should allow disabling custom color picker', () => { + const onChange = jest.fn(); + const { container } = render( ); @@ -125,15 +132,16 @@ describe( 'ColorPalette', () => { expect( container ).toMatchSnapshot(); } ); - test( 'should render dropdown and its content', async () => { + it( 'should render dropdown and its content', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); @@ -153,12 +161,38 @@ describe( 'ColorPalette', () => { expect( dropdownButton ).toBeVisible(); expect( - within( dropdownButton ).getByText( colors[ 0 ].name ) + within( dropdownButton ).getByText( EXAMPLE_COLORS[ 0 ].name ) ).toBeVisible(); expect( within( dropdownButton ).getByText( - colors[ 0 ].color.replace( '#', '' ) + EXAMPLE_COLORS[ 0 ].color.replace( '#', '' ) ) ).toBeVisible(); } ); + + it( 'should show the clear button by default', () => { + const onChange = jest.fn(); + + render( + + ); + + expect( + screen.getByRole( 'button', { name: 'Clear' } ) + ).toBeInTheDocument(); + } ); + + it( 'should show the clear button even when `colors` is an empty array', () => { + const onChange = jest.fn(); + + render( ); + + expect( + screen.getByRole( 'button', { name: 'Clear' } ) + ).toBeInTheDocument(); + } ); } ); From 4caa89368d66610c9fd5aed38ea803d60fd239d4 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:49:37 +0900 Subject: [PATCH 095/384] Bump caniuse-lite version (#46093) --- package-lock.json | 6 ++-- .../test/__snapshots__/build.js.snap | 32 +++++++++---------- .../test/__snapshots__/build.js.snap | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31d7463938053e..c539ceb114a268 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29230,9 +29230,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001352", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", - "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==" + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" }, "capital-case": { "version": "1.0.4", diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index 3feaba84de0338..0f918da3bb9ab4 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DependencyExtractionWebpackPlugin Webpack \`combine-assets\` should produce expected output: Asset file 'assets.php' should match snapshot 1`] = ` -" array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'cf268e19006bef774112'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '7f3970305cf0aecb54ab')); +" array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'bf200ecb3dcb6881a1f3'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '0af6c51a8e6ac934b85a')); " `; @@ -32,7 +32,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`dynamic-import\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => 'c8be4fceac30d1d00ca7'); +" array('wp-blob'), 'version' => '782d84ec5d7303bb6bd2'); " `; @@ -50,7 +50,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -73,7 +73,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`has-extension-suffix\` should produce expected output: Asset file 'index.min.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '49dba68ef238f954b358'); +" array('lodash', 'wp-blob'), 'version' => 'dabeb91f3cb9dd73d48d'); " `; @@ -96,21 +96,21 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => 'f7e2cb527e601f74f8bd'); +" array(), 'version' => 'bb85a9737103c7054b00'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: External modules should match snapshot 1`] = `Array []`; exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '143ed23d4b8be5611fcb'); +" array(), 'version' => '091ffcd70d94dd16e773'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: External modules should match snapshot 1`] = `Array []`; exports[`DependencyExtractionWebpackPlugin Webpack \`option-function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -133,7 +133,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`option-output-filename\` should produce expected output: Asset file 'main-foo.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -155,7 +155,7 @@ Array [ ] `; -exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{\\"dependencies\\":[\\"lodash\\"],\\"version\\":\\"4c42b9646049ad2e9438\\"}"`; +exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{\\"dependencies\\":[\\"lodash\\"],\\"version\\":\\"a8f35bfc9f46482cc48a\\"}"`; exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = ` Array [ @@ -168,7 +168,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`overrides\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '708c71445153f1d07e4a'); +" array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '2a29b245fc3d0509b5a8'); " `; @@ -207,17 +207,17 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => '09a0c551770a351c5ca7'); +" array('wp-blob'), 'version' => '4514ed711f6c035e0887'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'c9f00d690a9f72438910'); +" array('lodash', 'wp-blob'), 'version' => '168d32b5fb42f9e5d8ce'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '46ea0ff11ac53fa5e88b'); +" array(), 'version' => 'd3c2ce2cb84ff74b92e0'); " `; @@ -240,7 +240,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'd8c0ee89d933a3809c0e'); +" array('lodash', 'wp-blob'), 'version' => '04b9da7eff6fbfcb0452'); " `; @@ -263,7 +263,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -286,7 +286,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress-require\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '40370eb4ce6428562da6'); +" array('lodash', 'wp-blob'), 'version' => 'ed2bd4e7df46768bb3c2'); " `; diff --git a/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap b/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap index ec163c00bd93de..6d93ca7e74f211 100644 --- a/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ReadableJsAssetsWebpackPlugin should produce the expected output: Asset file index.js should match snapshot 1`] = ` -"/******/ (() => { // webpackBootstrap +"/******/ (function() { // webpackBootstrap var __webpack_exports__ = {}; function notMinified() { // eslint-disable-next-line no-console @@ -16,7 +16,7 @@ notMinified(); exports[`ReadableJsAssetsWebpackPlugin should produce the expected output: Asset file index.min.js should match snapshot 1`] = `"console.log(\\"hello\\");"`; exports[`ReadableJsAssetsWebpackPlugin should produce the expected output: Asset file view.js should match snapshot 1`] = ` -"/******/ (() => { // webpackBootstrap +"/******/ (function() { // webpackBootstrap var __webpack_exports__ = {}; function notMinified() { // eslint-disable-next-line no-console From d87548d19cbc3518da1da5be295bb89a4669d56a Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:02:38 +0800 Subject: [PATCH 096/384] Nav Block: Move color controls to support panel (#46049) This will mean they appear under the styles tab when the block inspector tabs experiment is enabled. --- .../src/navigation/edit/index.js | 280 +++++++++--------- 1 file changed, 147 insertions(+), 133 deletions(-) diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 319b7f3736a3c6..0a24063716fe0a 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -20,11 +20,12 @@ import { __experimentalUseHasRecursion as useHasRecursion, store as blockEditorStore, withColors, - PanelColorSettings, ContrastChecker, getColorClassName, Warning, + __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, __experimentalUseBlockOverlayActive as useBlockOverlayActive, + __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, } from '@wordpress/block-editor'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; @@ -529,144 +530,157 @@ function Navigation( { } ); }, [ isDraftNavigationMenu, navigationMenu ] ); + const colorGradientSettings = useMultipleOriginColorsAndGradients(); const stylingInspectorControls = ( - - { hasSubmenuIndicatorSetting && ( - - { isResponsive && ( - <> - + { overlayMenuPreview && ( + ) } - - { overlayMenuPreview && ( - - ) } - - ) } -

    { __( 'Overlay Menu' ) }

    - ) } - onChange={ ( value ) => - setAttributes( { overlayMenu: value } ) - } - isBlock - hideLabelFromVision - > - - - - - { hasSubmenus && ( - <> -

    { __( 'Submenus' ) }

    - { - setAttributes( { - openSubmenusOnClick: value, - ...( value && { - showSubmenuIcon: true, - } ), // Make sure arrows are shown when we toggle this on. - } ); - } } - label={ __( 'Open on click' ) } - /> - - { - setAttributes( { - showSubmenuIcon: value, - } ); - } } - disabled={ attributes.openSubmenusOnClick } - label={ __( 'Show arrow' ) } +

    { __( 'Overlay Menu' ) }

    + + setAttributes( { overlayMenu: value } ) + } + isBlock + hideLabelFromVision + > + - - ) } -
    - ) } - { hasColorSettings && ( - - { enableContrastChecking && ( - <> - - - - ) } - - ) } -
    + + { hasSubmenus && ( + <> +

    { __( 'Submenus' ) }

    + { + setAttributes( { + openSubmenusOnClick: value, + ...( value && { + showSubmenuIcon: true, + } ), // Make sure arrows are shown when we toggle this on. + } ); + } } + label={ __( 'Open on click' ) } + /> + + { + setAttributes( { + showSubmenuIcon: value, + } ); + } } + disabled={ attributes.openSubmenusOnClick } + label={ __( 'Show arrow' ) } + /> + + ) } +
    + ) } +
    + + { hasColorSettings && ( + <> + setTextColor(), + }, + { + colorValue: backgroundColor.color, + label: __( 'Background' ), + onColorChange: setBackgroundColor, + resetAllFilter: () => setBackgroundColor(), + }, + { + colorValue: overlayTextColor.color, + label: __( 'Submenu & overlay text' ), + onColorChange: setOverlayTextColor, + resetAllFilter: () => setOverlayTextColor(), + }, + { + colorValue: overlayBackgroundColor.color, + label: __( 'Submenu & overlay background' ), + onColorChange: setOverlayBackgroundColor, + resetAllFilter: () => + setOverlayBackgroundColor(), + }, + ] } + panelId={ clientId } + { ...colorGradientSettings } + gradients={ [] } + disableCustomGradients={ true } + /> + { enableContrastChecking && ( + <> + + + + ) } + + ) } + + ); // If the block has inner blocks, but no menu id, then these blocks are either: From 21fc1d3afabd5eb63de104ac59dc25943796c00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20B=C3=A4thge?= Date: Mon, 28 Nov 2022 07:21:48 +0100 Subject: [PATCH 097/384] Fix invalid attribute markup in core/home-link block (#46089) The `core/home-link` block produces invalid HTML markup, after f4e4ac25151175ae229d1a2f4a0d6129f9738801 introduced a stray `"` in front of the `rel` attribute. This PR removes that wrong quotation mark. --- packages/block-library/src/home-link/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index 5bf7aeda5505df..33f41057c936bb 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -128,7 +128,7 @@ function render_block_core_home_link( $attributes, $content, $block ) { $aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : ''; return sprintf( - '
  • %4$s
  • ', + '
  • %4$s
  • ', block_core_home_link_build_li_wrapper_attributes( $block->context ), esc_url( home_url() ), $aria_current, From a4eab09ba375fc65fab2f53950fa962bf221b6c5 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Mon, 28 Nov 2022 09:21:44 +0100 Subject: [PATCH 098/384] [Mobile] - Autocomplete - Fix regression related to the Enter key code (#45997) * Mobile - Fix issue on mobile and the autocomplete feature / slash inserter where the enter key would fail to create the block and instead it creates a new Paragraph block * Add KeyCodes to the AztecView mock --- packages/react-native-aztec/src/AztecView.js | 1 + packages/rich-text/src/component/index.native.js | 1 + test/native/__mocks__/@wordpress/react-native-aztec/index.js | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native-aztec/src/AztecView.js b/packages/react-native-aztec/src/AztecView.js index 456fde3ed8b694..f194aa67ffe99c 100644 --- a/packages/react-native-aztec/src/AztecView.js +++ b/packages/react-native-aztec/src/AztecView.js @@ -284,5 +284,6 @@ class AztecView extends Component { const RCTAztecView = requireNativeComponent( 'RCTAztecView', AztecView ); AztecView.InputState = AztecInputState; +AztecView.KeyCodes = KEYCODES; export default AztecView; diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 7fb9a25e1df63c..964620580e6869 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -372,6 +372,7 @@ export class RichText extends Component { this.customEditableOnKeyDown?.( { preventDefault: () => undefined, ...event, + key: RCTAztecView.KeyCodes[ event?.keyCode ], } ); this.handleDelete( event ); diff --git a/test/native/__mocks__/@wordpress/react-native-aztec/index.js b/test/native/__mocks__/@wordpress/react-native-aztec/index.js index f64e93af002952..1452aeb957c872 100644 --- a/test/native/__mocks__/@wordpress/react-native-aztec/index.js +++ b/test/native/__mocks__/@wordpress/react-native-aztec/index.js @@ -9,9 +9,11 @@ import { omit } from 'lodash'; */ import { forwardRef, useImperativeHandle, useRef } from '@wordpress/element'; -// Preserve the mock of AztecInputState to be exported with the AztecView mock. +// Preserve the mock of AztecInputState and AztecKeyCodes to be exported with the AztecView mock. const AztecInputState = jest.requireActual( '@wordpress/react-native-aztec' ) .default.InputState; +const AztecKeyCodes = jest.requireActual( '@wordpress/react-native-aztec' ) + .default.KeyCodes; const UNSUPPORTED_PROPS = [ 'style' ]; @@ -50,5 +52,6 @@ const RCTAztecView = ( { accessibilityLabel, text, ...rest }, ref ) => { const AztecView = forwardRef( RCTAztecView ); AztecView.InputState = AztecInputState; +AztecView.KeyCodes = AztecKeyCodes; export default AztecView; From 5a270b9733254ca5e5a4d26a2604ebe12e4b10f2 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:51:45 +0200 Subject: [PATCH 099/384] Lodash: Refactor editor away from _.filter() (#46059) --- .../src/components/document-outline/check.js | 8 +------- .../src/components/editor-notices/index.js | 19 ++++++------------- .../src/components/editor-snackbars/index.js | 11 +++-------- .../src/components/post-taxonomies/index.js | 10 ++-------- .../components/post-taxonomies/test/index.js | 2 +- 5 files changed, 13 insertions(+), 37 deletions(-) diff --git a/packages/editor/src/components/document-outline/check.js b/packages/editor/src/components/document-outline/check.js index 324b9380ca63a3..0ae0b3435e1e46 100644 --- a/packages/editor/src/components/document-outline/check.js +++ b/packages/editor/src/components/document-outline/check.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -10,8 +5,7 @@ import { withSelect } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; function DocumentOutlineCheck( { blocks, children } ) { - const headings = filter( - blocks, + const headings = blocks.filter( ( block ) => block.name === 'core/heading' ); diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index 1dd7b8e0e31bf7..ec4bd04d689200 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -17,14 +12,12 @@ import { store as noticesStore } from '@wordpress/notices'; import TemplateValidationNotice from '../template-validation-notice'; export function EditorNotices( { notices, onRemove } ) { - const dismissibleNotices = filter( notices, { - isDismissible: true, - type: 'default', - } ); - const nonDismissibleNotices = filter( notices, { - isDismissible: false, - type: 'default', - } ); + const dismissibleNotices = notices.filter( + ( { isDismissible, type } ) => isDismissible && type === 'default' + ); + const nonDismissibleNotices = notices.filter( + ( { isDismissible, type } ) => ! isDismissible && type === 'default' + ); return ( <> diff --git a/packages/editor/src/components/editor-snackbars/index.js b/packages/editor/src/components/editor-snackbars/index.js index 6dc01435eb4b45..57f0fe4bf88af5 100644 --- a/packages/editor/src/components/editor-snackbars/index.js +++ b/packages/editor/src/components/editor-snackbars/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -16,9 +11,9 @@ export default function EditorSnackbars() { [] ); const { removeNotice } = useDispatch( noticesStore ); - const snackbarNotices = filter( notices, { - type: 'snackbar', - } ); + const snackbarNotices = notices.filter( + ( { type } ) => type === 'snackbar' + ); return ( + const availableTaxonomies = ( taxonomies ?? [] ).filter( ( taxonomy ) => taxonomy.types.includes( postType ) ); - const visibleTaxonomies = filter( - availableTaxonomies, + const visibleTaxonomies = availableTaxonomies.filter( // In some circumstances .visibility can end up as undefined so optional chaining operator required. // https://github.com/WordPress/gutenberg/issues/40326 ( taxonomy ) => taxonomy.visibility?.show_ui diff --git a/packages/editor/src/components/post-taxonomies/test/index.js b/packages/editor/src/components/post-taxonomies/test/index.js index ef1a446be3d71a..0a388143061750 100644 --- a/packages/editor/src/components/post-taxonomies/test/index.js +++ b/packages/editor/src/components/post-taxonomies/test/index.js @@ -85,7 +85,7 @@ describe( 'PostTaxonomies', () => { } ); it( 'should render no children if taxonomy data not available', () => { - const taxonomies = {}; + const taxonomies = null; const { container } = render( From 3835cda96c1c2688bb06a617abdd197ebe7261e6 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:02 +0200 Subject: [PATCH 100/384] Lodash: Refactor edit-post away from _.filter() (#46060) --- .../src/components/block-manager/index.js | 15 +++++---------- .../preferences-modal/meta-boxes-section.js | 5 ++--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/edit-post/src/components/block-manager/index.js b/packages/edit-post/src/components/block-manager/index.js index bc141500390ad7..d670ea52cf569a 100644 --- a/packages/edit-post/src/components/block-manager/index.js +++ b/packages/edit-post/src/components/block-manager/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -92,15 +87,15 @@ function BlockManager( { + blockType.category === category.slug + ) } /> ) ) } ! category ) } /> diff --git a/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js b/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js index 857eb27910bed6..0ad7f518bcb705 100644 --- a/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js +++ b/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, map } from 'lodash'; +import { map } from 'lodash'; /** * WordPress dependencies @@ -23,8 +23,7 @@ export function MetaBoxesSection( { ...sectionProps } ) { // The 'Custom Fields' meta box is a special case that we handle separately. - const thirdPartyMetaBoxes = filter( - metaBoxes, + const thirdPartyMetaBoxes = metaBoxes.filter( ( { id } ) => id !== 'postcustom' ); From 15934e355cca1ce5b345036db9db4e97f70bcd78 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:22 +0200 Subject: [PATCH 101/384] Lodash: Refactor block editor away from _.filter() (#46064) --- packages/block-editor/src/store/selectors.js | 27 ++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 0345b29ff93323..7ddb6603f2ed50 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, find, filter, orderBy } from 'lodash'; +import { map, find, orderBy } from 'lodash'; import createSelector from 'rememo'; /** @@ -509,24 +509,20 @@ export const getBlockParentsByBlockName = createSelector( ( state, clientId, blockName, ascending = false ) => { const parents = getBlockParents( state, clientId, ascending ); return map( - filter( - map( parents, ( id ) => ( { - id, - name: getBlockName( state, id ), - } ) ), - ( { name } ) => { - if ( Array.isArray( blockName ) ) { - return blockName.includes( name ); - } - return name === blockName; + map( parents, ( id ) => ( { + id, + name: getBlockName( state, id ), + } ) ).filter( ( { name } ) => { + if ( Array.isArray( blockName ) ) { + return blockName.includes( name ); } - ), + return name === blockName; + } ), ( { id } ) => id ); }, ( state ) => [ state.blocks.parents ] ); - /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. * @@ -2167,7 +2163,7 @@ export const __experimentalGetAllowedBlocks = createSelector( return; } - return filter( getBlockTypes(), ( blockType ) => + return getBlockTypes().filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); }, @@ -2292,8 +2288,7 @@ const getAllAllowedPatterns = createSelector( export const __experimentalGetAllowedPatterns = createSelector( ( state, rootClientId = null ) => { const availableParsedPatterns = getAllAllowedPatterns( state ); - const patternsAllowed = filter( - availableParsedPatterns, + const patternsAllowed = availableParsedPatterns.filter( ( { blocks } ) => blocks.every( ( { name } ) => canInsertBlockType( state, name, rootClientId ) From 21fb80e6094dc335856e47210e0f761382ab1829 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:46 +0200 Subject: [PATCH 102/384] Lodash: Refactor core-data away from _.filter() (#46066) --- packages/core-data/src/queried-data/reducer.js | 4 ++-- packages/core-data/src/selectors.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 9db1b7dad741f8..e47f0220156a75 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, omit, filter, mapValues } from 'lodash'; +import { map, omit, mapValues } from 'lodash'; /** * WordPress dependencies @@ -233,7 +233,7 @@ const queries = ( state = {}, action ) => { return mapValues( state, ( contextQueries ) => { return mapValues( contextQueries, ( queryItems ) => { - return filter( queryItems, ( queryId ) => { + return queryItems.filter( ( queryId ) => { return ! removedItems[ queryId ]; } ); } ); diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 4151a94824acb5..93306d2226e7a2 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -2,7 +2,7 @@ * External dependencies */ import createSelector from 'rememo'; -import { set, map, find, get, filter } from 'lodash'; +import { set, map, find, get } from 'lodash'; /** * WordPress dependencies @@ -183,7 +183,7 @@ export function getEntitiesByKind( state: State, kind: string ): Array< any > { * @return Array of entities with config matching kind. */ export function getEntitiesConfig( state: State, kind: string ): Array< any > { - return filter( state.entities.config, { kind } ); + return state.entities.config.filter( ( entity ) => entity.kind === kind ); } /** From 2904f8de2cfc13004842e38c1964b43b242a32b5 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 28 Nov 2022 11:03:42 +0100 Subject: [PATCH 103/384] URLInput: keep the search results label in sync with the results list (#45806) * URLInput: reorganize the renderSuggestion condition * URLInput: simplify shouldShowInitialSuggestions which always runs with initial state * URLInput: don't updateSuggestions on change, it's done by cDU lifecycle * URLInput: cancel suggestionsRequest when updated value means no request * URLInput: set empty suggestions when updated value means no request * URLInput: move isUpdatingSuggestions to state * URLInput: put the current input value to state, in sync with search results * LinkControl: don't send currentInputValue to URLInput, it already does it internally * URLInput: mention the currentInputValue param for renderSuggestions in block-editor changelog --- packages/block-editor/CHANGELOG.md | 4 + .../src/components/link-control/README.md | 2 +- .../components/link-control/search-input.js | 1 - .../src/components/url-input/index.js | 130 ++++++++---------- 4 files changed, 62 insertions(+), 75 deletions(-) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 4d37bf5737599c..0ce86cc9cb1103 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancement + +- `URLInput`: the `renderSuggestions` callback prop now receives `currentInputValue` as a new parameter ([45806](https://github.com/WordPress/gutenberg/pull/45806)). + ## 10.5.0 (2022-11-16) ### Enhancement diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index 3d3dd7b0aaa210..c3fc7262adb956 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -322,10 +322,10 @@ The following properties are provided by URLInput: - suggestions - selectedSuggestion - suggestionsListProps +- currentInputValue The following extra properties are provided by LinkControlSearchInput: -- currentInputValue - createSuggestionButtonText - handleSuggestionClick - instanceId diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index cabe52f907469e..95c925a45d8303 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -81,7 +81,6 @@ const LinkControlSearchInput = forwardRef( ...props, instanceId, withCreateSuggestion, - currentInputValue: value, createSuggestionButtonText, suggestionsQuery, handleSuggestionClick: ( suggestion ) => { diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 3858f3983103a9..0eb97730e15d18 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -60,13 +60,14 @@ class URLInput extends Component { this.suggestionNodes = []; - this.isUpdatingSuggestions = false; + this.suggestionsRequest = null; this.state = { suggestions: [], showSuggestions: false, + isUpdatingSuggestions: false, + suggestionsValue: null, selectedSuggestion: null, - suggestionsListboxId: '', suggestionOptionIdPrefix: '', }; @@ -103,7 +104,7 @@ class URLInput extends Component { if ( prevProps.value !== value && ! this.props.disableSuggestions && - ! this.isUpdatingSuggestions + ! this.state.isUpdatingSuggestions ) { if ( value?.length ) { // If the new value is not empty we need to update with suggestions for it. @@ -123,7 +124,7 @@ class URLInput extends Component { componentWillUnmount() { this.suggestionsRequest?.cancel?.(); - delete this.suggestionsRequest; + this.suggestionsRequest = null; } bindSuggestionNode( index ) { @@ -133,14 +134,10 @@ class URLInput extends Component { } shouldShowInitialSuggestions() { - const { suggestions } = this.state; const { __experimentalShowInitialSuggestions = false, value } = this.props; return ( - ! this.isUpdatingSuggestions && - __experimentalShowInitialSuggestions && - ! ( value && value.length ) && - ! ( suggestions && suggestions.length ) + __experimentalShowInitialSuggestions && ! ( value && value.length ) ); } @@ -170,8 +167,13 @@ class URLInput extends Component { ! isInitialSuggestions && ( value.length < 2 || ( ! handleURLSuggestions && isURL( value ) ) ) ) { + this.suggestionsRequest?.cancel?.(); + this.suggestionsRequest = null; + this.setState( { + suggestions: [], showSuggestions: false, + suggestionsValue: value, selectedSuggestion: null, loading: false, } ); @@ -179,9 +181,8 @@ class URLInput extends Component { return; } - this.isUpdatingSuggestions = true; - this.setState( { + isUpdatingSuggestions: true, selectedSuggestion: null, loading: true, } ); @@ -201,6 +202,8 @@ class URLInput extends Component { this.setState( { suggestions, + isUpdatingSuggestions: false, + suggestionsValue: value, loading: false, showSuggestions: !! suggestions.length, } ); @@ -224,15 +227,16 @@ class URLInput extends Component { 'assertive' ); } - this.isUpdatingSuggestions = false; } ) .catch( () => { - if ( this.suggestionsRequest === request ) { - this.setState( { - loading: false, - } ); - this.isUpdatingSuggestions = false; + if ( this.suggestionsRequest !== request ) { + return; } + + this.setState( { + isUpdatingSuggestions: false, + loading: false, + } ); } ); // Note that this assignment is handled *before* the async search request @@ -241,12 +245,7 @@ class URLInput extends Component { } onChange( event ) { - const inputValue = event.target.value; - - this.props.onChange( inputValue ); - if ( ! this.props.disableSuggestions ) { - this.updateSuggestions( inputValue ); - } + this.props.onChange( event.target.value ); } onFocus() { @@ -258,7 +257,7 @@ class URLInput extends Component { if ( value && ! disableSuggestions && - ! this.isUpdatingSuggestions && + ! this.state.isUpdatingSuggestions && ! ( suggestions && suggestions.length ) ) { // Ensure the suggestions are updated with the current input value. @@ -490,19 +489,22 @@ class URLInput extends Component { const { className, __experimentalRenderSuggestions: renderSuggestions, - value = '', - __experimentalShowInitialSuggestions = false, } = this.props; const { showSuggestions, suggestions, + suggestionsValue, selectedSuggestion, suggestionsListboxId, suggestionOptionIdPrefix, loading, } = this.state; + if ( ! showSuggestions || suggestions.length === 0 ) { + return null; + } + const suggestionsListProps = { id: suggestionsListboxId, ref: this.autocompleteRef, @@ -519,11 +521,7 @@ class URLInput extends Component { }; }; - if ( - isFunction( renderSuggestions ) && - showSuggestions && - !! suggestions.length - ) { + if ( isFunction( renderSuggestions ) ) { return renderSuggestions( { suggestions, selectedSuggestion, @@ -531,52 +529,38 @@ class URLInput extends Component { buildSuggestionItemProps, isLoading: loading, handleSuggestionClick: this.handleOnClick, - isInitialSuggestions: - __experimentalShowInitialSuggestions && - ! ( value && value.length ), + isInitialSuggestions: ! suggestionsValue?.length, + currentInputValue: suggestionsValue, } ); } - if ( - ! isFunction( renderSuggestions ) && - showSuggestions && - !! suggestions.length - ) { - return ( - -
    - { suggestions.map( ( suggestion, index ) => ( - - ) ) } -
    -
    - ); - } - return null; + ) } + onClick={ () => this.handleOnClick( suggestion ) } + > + { suggestion.title } + + ) ) } + + + ); } } From ec2bdc59c4c4cdf8d86cdd432ee30eaca6f14eed Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:03:06 +0200 Subject: [PATCH 104/384] Lodash: Refactor blocks away from _.filter() (#46062) --- packages/blocks/src/store/reducer.js | 16 +++-- packages/blocks/src/store/selectors.js | 4 +- packages/blocks/src/store/test/selectors.js | 68 +++++++++++---------- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 6770dd6b67f26b..29bd7c9ef02aa7 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, find, get, isEmpty, map, mapValues } from 'lodash'; +import { find, get, isEmpty, map, mapValues } from 'lodash'; /** * WordPress dependencies @@ -144,8 +144,11 @@ export function blockStyles( state = {}, action ) { case 'REMOVE_BLOCK_STYLES': return { ...state, - [ action.blockName ]: filter( - get( state, [ action.blockName ], [] ), + [ action.blockName ]: get( + state, + [ action.blockName ], + [] + ).filter( ( style ) => action.styleNames.indexOf( style.name ) === -1 ), }; @@ -195,8 +198,11 @@ export function blockVariations( state = {}, action ) { case 'REMOVE_BLOCK_VARIATIONS': return { ...state, - [ action.blockName ]: filter( - get( state, [ action.blockName ], [] ), + [ action.blockName ]: get( + state, + [ action.blockName ], + [] + ).filter( ( variation ) => action.variationNames.indexOf( variation.name ) === -1 ), diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 298d7fc12141aa..27cacef4085820 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -3,7 +3,7 @@ */ import createSelector from 'rememo'; import removeAccents from 'remove-accents'; -import { filter, get, map } from 'lodash'; +import { get, map } from 'lodash'; /** * WordPress dependencies @@ -554,7 +554,7 @@ export function getGroupingBlockName( state ) { export const getChildBlockNames = createSelector( ( state, blockName ) => { return map( - filter( state.blockTypes, ( blockType ) => { + getBlockTypes( state ).filter( ( blockType ) => { return blockType.parent?.includes( blockName ); } ), ( { name } ) => name diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 8793e1e354e44f..1fda11d72311a3 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -86,26 +86,28 @@ describe( 'selectors', () => { describe( 'getChildBlockNames', () => { it( 'should return an empty array if state is empty', () => { - const state = {}; + const state = { + blockTypes: {}, + }; expect( getChildBlockNames( state, 'parent1' ) ).toHaveLength( 0 ); } ); it( 'should return an empty array if no children exist', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + parent3: { name: 'parent3', }, - ], + }, }; expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); @@ -113,15 +115,15 @@ describe( 'selectors', () => { it( 'should return an empty array if the parent block is not found', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + parent1: { name: 'parent1', }, - ], + }, }; expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); @@ -129,29 +131,29 @@ describe( 'selectors', () => { it( 'should return an array with the child block names', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + child3: { name: 'child3', parent: [ 'parent1' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ @@ -162,25 +164,25 @@ describe( 'selectors', () => { it( 'should return an array with the child block names even if only one child exists', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ @@ -190,29 +192,29 @@ describe( 'selectors', () => { it( 'should return an array with the child block names even if children have multiple parents', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent1', 'parent2' ], }, - { + child3: { name: 'child3', parent: [ 'parent1' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ From 606aee8a981f9fd402297bbfa1cf72c84bcd8bde Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 28 Nov 2022 14:35:46 +0100 Subject: [PATCH 105/384] useBlockEditorSettings: return const empty array to avoid rerenders (#46117) --- .../src/components/provider/use-block-editor-settings.js | 4 +++- .../components/provider/use-block-editor-settings.native.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 69b4f9b42cfc63..3f4ed5a15fe370 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -17,6 +17,8 @@ import { __ } from '@wordpress/i18n'; import { mediaUpload } from '../../utils'; import { store as editorStore } from '../../store'; +const EMPTY_BLOCKS_LIST = []; + /** * React hook used to compute the block editor settings to use for the post editor. * @@ -51,7 +53,7 @@ function useBlockEditorSettings( settings, hasTemplate ) { 'wp_block', { per_page: -1 } ) - : [], // Reusable blocks are fetched in the native version of this hook. + : EMPTY_BLOCKS_LIST, // Reusable blocks are fetched in the native version of this hook. hasUploadPermissions: canUser( 'create', 'media' ) ?? true, userCanCreatePages: canUser( 'create', 'pages' ), pageOnFront: siteSettings?.page_on_front, diff --git a/packages/editor/src/components/provider/use-block-editor-settings.native.js b/packages/editor/src/components/provider/use-block-editor-settings.native.js index 53d703ee762faa..96ff12d2d76db9 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.native.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.native.js @@ -11,6 +11,8 @@ import { store as coreStore } from '@wordpress/core-data'; import useBlockEditorSettings from './use-block-editor-settings.js'; import { store as editorStore } from '../../store'; +const EMPTY_BLOCKS_LIST = []; + function useNativeBlockEditorSettings( settings, hasTemplate ) { const capabilities = settings.capabilities ?? {}; const editorSettings = useBlockEditorSettings( settings, hasTemplate ); @@ -27,7 +29,7 @@ function useNativeBlockEditorSettings( settings, hasTemplate ) { // Related issue: https://github.com/wordpress-mobile/gutenberg-mobile/issues/2661 { per_page: 100 } ) - : [], + : EMPTY_BLOCKS_LIST, isTitleSelected: select( editorStore ).isPostTitleSelected(), }; }, From ca377b5432cc8cad155eaa3e267fa07fb24fbb48 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:55:59 +0900 Subject: [PATCH 106/384] LinkedButton: remove unnecessary span tag (#46063) * LinkedButton: remove unnecessary span tag * Update changelog --- packages/components/CHANGELOG.md | 1 + .../src/box-control/linked-button.js | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 22a08c2f0dd08c..d2580e0b009b5f 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ ### Internal +- `LinkedButton`: remove unnecessary `span` tag ([#46063](https://github.com/WordPress/gutenberg/pull/46063)) - NumberControl: refactor styles/tests/stories to TypeScript, replace fireEvent with user-event ([#45990](https://github.com/WordPress/gutenberg/pull/45990)). ## 22.1.0 (2022-11-16) diff --git a/packages/components/src/box-control/linked-button.js b/packages/components/src/box-control/linked-button.js index 39de0e894783f2..67fb1cebf7b091 100644 --- a/packages/components/src/box-control/linked-button.js +++ b/packages/components/src/box-control/linked-button.js @@ -13,19 +13,16 @@ import Tooltip from '../tooltip'; export default function LinkedButton( { isLinked, ...props } ) { const label = isLinked ? __( 'Unlink sides' ) : __( 'Link sides' ); - // TODO: Remove span after merging https://github.com/WordPress/gutenberg/pull/44198 return ( - - + + ); + + const button = screen.getByRole( 'button', { name: 'Hover Me!' } ); + await user.hover( button ); + + expect( await screen.findByText( 'shortcut text' ) ).toBeVisible(); + } ); + + it( 'should render the shortcut display text and aria-label when an object is passed as the shortcut with the correct properties', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + + + ); + + const button = screen.getByRole( 'button', { name: 'Hover Me!' } ); + await user.hover( button ); + + expect( + await screen.findByLabelText( 'shortcut label' ) + ).toHaveTextContent( 'shortcut text' ); + } ); } ); } ); From 4d940e4e32ab56b5096f0a5387417c62e6a6a05d Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 29 Nov 2022 15:38:00 +0400 Subject: [PATCH 117/384] Gallery: Use unbound query when fetching image details (#46143) --- packages/block-library/src/gallery/use-get-media.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/gallery/use-get-media.js b/packages/block-library/src/gallery/use-get-media.js index 36559447ae291f..a6dbe9faabf0b9 100644 --- a/packages/block-library/src/gallery/use-get-media.js +++ b/packages/block-library/src/gallery/use-get-media.js @@ -28,7 +28,7 @@ export default function useGetMedia( innerBlockImages ) { return ( select( coreStore ).getMediaItems( { include: imageIds.join( ',' ), - per_page: imageIds.length, + per_page: -1, orderby: 'include', } ) ?? EMPTY_IMAGE_MEDIA ); From 8cf5051bc76000710cfd043e305e1a3f010c2691 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:22:04 +0200 Subject: [PATCH 118/384] Components: Fix no-node-access violations in Card tests (#46158) --- packages/components/src/card/test/index.tsx | 52 +++++++++++++-------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/components/src/card/test/index.tsx b/packages/components/src/card/test/index.tsx index ee10655fae0b21..fb1c6d972aa698 100644 --- a/packages/components/src/card/test/index.tsx +++ b/packages/components/src/card/test/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -38,27 +38,39 @@ describe( 'Card', () => { } ); it( 'should remove borders when the isBorderless prop is true', () => { - const { rerender, container } = render( - Code is Poetry + const { rerender } = render( + Code is Poetry ); - expect( container.firstChild ).not.toHaveStyle( + + expect( screen.getByTestId( 'card-wrapper' ) ).not.toHaveStyle( 'box-shadow: none' ); - rerender( Code is Poetry ); - expect( container.firstChild ).toHaveStyle( 'box-shadow: none' ); + rerender( + + Code is Poetry + + ); + + expect( screen.getByTestId( 'card-wrapper' ) ).toHaveStyle( + 'box-shadow: none' + ); } ); it( 'should add rounded border when the isRounded prop is true', () => { - const { container: containerRounded } = render( - Code is Poetry + render( + + Code is Poetry + ); - const { container: containerSquared } = render( - Code is Poetry + render( + + Code is Poetry + ); expect( - containerRounded.firstElementChild - ).toMatchStyleDiffSnapshot( containerSquared.firstElementChild ); + screen.getByTestId( 'card-rounded' ) + ).toMatchStyleDiffSnapshot( screen.getByTestId( 'card-squared' ) ); } ); it( 'should show a box shadow when the elevation prop is greater than 0', () => { @@ -94,7 +106,7 @@ describe( 'Card', () => { it( 'should warn when the isElevated prop is passed', () => { // `isElevated` is automatically converted to `elevation="2"` const { container } = render( - Code is Poetry + Code is Poetry ); expect( container ).toMatchSnapshot(); @@ -103,7 +115,7 @@ describe( 'Card', () => { it( 'should pass the isBorderless and isSize props from its context to its sub-components', () => { const { container: withoutBorderLarge } = render( - + Header Body Footer @@ -121,14 +133,14 @@ describe( 'Card', () => { it( 'should get the isBorderless and isSize props (passed from its context) overriddenwhen the same props is specified directly on the component', () => { const { container: withoutBorder } = render( - + Header Body Footer ); const { container: withBorder } = render( - + Header @@ -165,7 +177,7 @@ describe( 'Card', () => { it( 'should render with a darker background color when isShady is true', () => { const { container } = render( Header ); const { container: containerShady } = render( - Header + Header ); expect( container ).toMatchDiffSnapshot( containerShady ); } ); @@ -175,7 +187,7 @@ describe( 'Card', () => { it( 'should render with a darker background color when isShady is true', () => { const { container } = render( Footer ); const { container: containerShady } = render( - Footer + Footer ); expect( container ).toMatchDiffSnapshot( containerShady ); } ); @@ -193,14 +205,14 @@ describe( 'Card', () => { it( 'should render with a darker background color when isShady is true', () => { const { container } = render( Body ); const { container: containerShady } = render( - Body + Body ); expect( container ).toMatchDiffSnapshot( containerShady ); } ); it( 'should allow scrolling content with the scrollable prop is true', () => { const { container: containerScrollable } = render( - Body + Body ); const { container } = render( Body ); expect( container ).toMatchDiffSnapshot( containerScrollable ); From 59ed345d8373fb25d10a4fbb510f63f0e02305cf Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:31:32 +0200 Subject: [PATCH 119/384] Components: Fix no-node-access violations in Disabled tests (#46156) --- .../components/src/disabled/test/index.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/components/src/disabled/test/index.tsx b/packages/components/src/disabled/test/index.tsx index cedcdad872701f..fddbd1159637bb 100644 --- a/packages/components/src/disabled/test/index.tsx +++ b/packages/components/src/disabled/test/index.tsx @@ -18,48 +18,58 @@ describe( 'Disabled', () => { ); it( 'will disable all fields', () => { - const { container } = render( - + render( +
    ); - expect( container.firstChild ).toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).toHaveAttribute( + 'inert' + ); } ); it( 'should cleanly un-disable via reconciliation', () => { const MaybeDisable = ( { isDisabled = true } ) => isDisabled ? ( - + ) : ( ); - const { container, rerender } = render( ); + const { rerender } = render( ); - expect( container.firstChild ).toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).toHaveAttribute( + 'inert' + ); rerender( ); - expect( container.firstChild ).not.toHaveAttribute( 'inert' ); + expect( + screen.queryByTestId( 'disabled-wrapper' ) + ).not.toBeInTheDocument(); } ); it( 'will disable or enable descendant fields based on the isDisabled prop value', () => { const MaybeDisable = ( { isDisabled = true } ) => ( - + ); - const { rerender, container } = render( ); + const { rerender } = render( ); - expect( container.firstChild ).toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).toHaveAttribute( + 'inert' + ); rerender( ); - expect( container.firstChild ).not.toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).not.toHaveAttribute( + 'inert' + ); } ); it( 'should preserve input values when toggling the isDisabled prop', async () => { From 2b8a42dd5facce243f8670ba6af7870f16eee722 Mon Sep 17 00:00:00 2001 From: Devanshi Joshi <86941664+devanshijoshi9@users.noreply.github.com> Date: Tue, 29 Nov 2022 19:22:19 +0530 Subject: [PATCH 120/384] Components: ToggleControl text overflows when it has a long label (#45962) * fix: Add style to resolve togglecontrol lable text overflow * Feat: Remove style.scss file from togglecontrol component * Feat: Use FlexBlock component instead of new stylesheet * Feat: Add changelog for ToggleControl component * Feat: Run test:unit:update script to update Snapshot --- packages/components/CHANGELOG.md | 1 + .../components/src/toggle-control/index.tsx | 6 +- .../enable-custom-fields.js.snap | 60 +++++++++++++++++-- .../test/__snapshots__/index.js.snap | 53 +++++++++++----- .../__snapshots__/meta-boxes-section.js.snap | 57 ++++++++++++++++-- 5 files changed, 151 insertions(+), 26 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 025a767c061a92..51fd7a415b79f6 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -46,6 +46,7 @@ - `Icon`: Making size prop work for icon components using dash icon strings ([#45593](https://github.com/WordPress/gutenberg/pull/45593)) - `ToolsPanelItem`: Prevent unintended calls to onDeselect when parent panel is remounted and item is rendered via SlotFill ([#45673](https://github.com/WordPress/gutenberg/pull/45673)) - `ColorPicker`: Prevent all number fields from becoming "0" when one of them is an empty string ([#45649](https://github.com/WordPress/gutenberg/pull/45649)). +- `ToggleControl`: Fix toggle control label text overflow ([#45962](https://github.com/WordPress/gutenberg/pull/45962)). ### Internal diff --git a/packages/components/src/toggle-control/index.tsx b/packages/components/src/toggle-control/index.tsx index 4dfcda30e7cab4..f71c807b9d4fca 100644 --- a/packages/components/src/toggle-control/index.tsx +++ b/packages/components/src/toggle-control/index.tsx @@ -12,6 +12,7 @@ import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ +import { FlexBlock } from '../flex'; import FormToggle from '../form-toggle'; import BaseControl from '../base-control'; import type { WordPressComponentProps } from '../ui/context/wordpress-component'; @@ -94,12 +95,13 @@ export function ToggleControl( { aria-describedby={ describedBy } disabled={ disabled } /> - + ); diff --git a/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap b/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap index 2485d91e9fdb9d..a405a1c39ee1cd 100644 --- a/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap +++ b/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap @@ -41,6 +41,17 @@ exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
    @@ -134,6 +147,17 @@ exports[`EnableCustomFieldsOption renders a checked checkbox when custom fields min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
    @@ -217,6 +243,17 @@ exports[`EnableCustomFieldsOption renders an unchecked checkbox and a confirmati min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
    @@ -311,6 +350,17 @@ exports[`EnableCustomFieldsOption renders an unchecked checkbox when custom fiel min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
    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 f9f7be89c82a0c..627eb2d284ddf3 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 @@ -42,6 +42,17 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active } .emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.emotion-8 { margin-top: calc(4px * 2); margin-bottom: 0; font-size: 12px; @@ -186,7 +197,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Review settings, such as visibility and tags. @@ -250,7 +263,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Reduce visual distractions by hiding the toolbar and other elements to focus on writing. @@ -296,7 +311,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Highlights the current block and fades other content. @@ -342,7 +359,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Show text instead of icons on buttons. @@ -388,7 +407,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Opens the block list view sidebar by default. @@ -434,7 +455,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Make the editor look like your theme. @@ -480,7 +503,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

    Shows block breadcrumbs at the bottom of the editor. diff --git a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap index 087120098c5831..2246e0f12fdef7 100644 --- a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap @@ -41,6 +41,17 @@ exports[`MetaBoxesSection renders a Custom Fields option 1`] = ` min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +

    @@ -83,7 +94,9 @@ exports[`MetaBoxesSection renders a Custom Fields option 1`] = ` />