diff --git a/.eslintrc.js b/.eslintrc.js index 87a98f5429d10..0fc100088bb56 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -81,6 +81,7 @@ module.exports = { importNames: [ 'camelCase', 'capitalize', + 'castArray', 'chunk', 'clamp', 'cloneDeep', @@ -96,6 +97,7 @@ module.exports = { 'differenceWith', 'dropRight', 'each', + 'escape', 'escapeRegExp', 'every', 'extend', @@ -112,6 +114,7 @@ module.exports = { 'fromPairs', 'has', 'identity', + 'includes', 'invoke', 'isArray', 'isBoolean', @@ -146,6 +149,7 @@ module.exports = { 'reverse', 'size', 'snakeCase', + 'some', 'sortBy', 'startCase', 'startsWith', @@ -166,6 +170,8 @@ module.exports = { 'uniqWith', 'upperFirst', 'values', + 'without', + 'words', 'xor', 'zip', ], @@ -322,8 +328,20 @@ module.exports = { }, { files: [ '**/test/**/*.js' ], - excludedFiles: [ '**/*.@(android|ios|native).js' ], - extends: [ 'plugin:jest-dom/recommended' ], + excludedFiles: [ + '**/*.@(android|ios|native).js', + 'packages/react-native-*/**/*.js', + 'test/native/**/*.js', + 'test/e2e/**/*.[tj]s', + ], + extends: [ + 'plugin:jest-dom/recommended', + 'plugin:testing-library/react', + ], + rules: { + 'testing-library/no-container': 'off', + 'testing-library/no-node-access': 'off', + }, }, { files: [ 'packages/e2e-test*/**/*.js' ], diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 59542a88ffa3d..cd5fa3082d7d7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,9 +2,9 @@ version: 2 updates: - # Check for updates to GitHub Actions. - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - open-pull-requests-limit: 10 + # Check for updates to GitHub Actions. + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'daily' + open-pull-requests-limit: 10 diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 82758b78e1f29..92d323f8dc13c 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -180,7 +180,7 @@ jobs: NO_CHECKS: 'true' - name: Upload artifact - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 with: name: gutenberg-plugin path: ./gutenberg.zip @@ -203,7 +203,7 @@ jobs: - name: Upload release notes artifact if: ${{ needs.bump-version.outputs.new_version }} - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 with: name: release-notes path: ./release-notes.txt @@ -266,12 +266,12 @@ jobs: run: echo ::set-output name=version::$(echo $VERSION | cut -d / -f 3 | sed 's/-rc./ RC/' ) - name: Download Plugin Zip Artifact - uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # v3.0.0 + uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1 with: name: gutenberg-plugin - name: Download Release Notes Artifact - uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # v3.0.0 + uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1 with: name: release-notes diff --git a/.github/workflows/end2end-test-playwright.yml b/.github/workflows/end2end-test-playwright.yml index c65f56b824c91..e0aabb17afa01 100644 --- a/.github/workflows/end2end-test-playwright.yml +++ b/.github/workflows/end2end-test-playwright.yml @@ -39,7 +39,7 @@ jobs: - name: Install Playwright dependencies run: | - npx playwright install chromium --with-deps + npx playwright install chromium firefox webkit --with-deps - name: Install WordPress and start the server run: | @@ -47,18 +47,18 @@ jobs: - name: Run the tests run: | - npm run test:e2e:playwright + xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:playwright - name: Archive debug artifacts (screenshots, traces) - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: failures-artifacts - path: artifacts + path: artifacts/test-results if-no-files-found: ignore - name: Archive flaky tests report - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: flaky-tests-report-playwright diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 5dadd996945a7..172d3b090eaf0 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -49,7 +49,7 @@ jobs: $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == ${{ matrix.part }} - 1' < ~/.jest-e2e-tests ) - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: failures-artifacts @@ -57,7 +57,7 @@ jobs: if-no-files-found: ignore - name: Archive flaky tests report - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: flaky-tests-report-${{ matrix.part }} diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 27b89697f9656..e878724c1b5f1 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -35,7 +35,7 @@ jobs: - name: Compare performance with trunk if: github.event_name == 'pull_request' - run: ./bin/plugin/cli.js perf --ci $GITHUB_SHA trunk --tests-branch $GITHUB_SHA + run: ./bin/plugin/cli.js perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA - name: Compare performance with current WordPress Core and previous Gutenberg versions if: github.event_name == 'release' @@ -50,7 +50,7 @@ jobs: WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - ./bin/plugin/cli.js perf --ci "wp/$WP_MAJOR" "$PREVIOUS_RELEASE_BRANCH" "$CURRENT_RELEASE_BRANCH" --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf "wp/$WP_MAJOR" "$PREVIOUS_RELEASE_BRANCH" "$CURRENT_RELEASE_BRANCH" --wp-version "$WP_MAJOR" - name: Compare performance with base branch if: github.event_name == 'push' @@ -61,9 +61,9 @@ jobs: WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - ./bin/plugin/cli.js perf --ci $GITHUB_SHA debd225d007f4e441ceec80fbd6fa96653f94737 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf $GITHUB_SHA debd225d007f4e441ceec80fbd6fa96653f94737 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - - uses: actions/github-script@100527700e8b29ca817ac0e0dfbfc5e8ff38edda # v6.3.2 + - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 # v6.3.3 if: github.event_name == 'push' id: commit-timestamp with: diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 5b35e24d091cb..dc16ff160495b 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - name: Use desired version of Java - uses: actions/setup-java@a18c333f3f14249953dab3e186e5e21bf3390f1d # v3.5.1 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # v3.6.0 with: distribution: 'temurin' java-version: '11' @@ -52,13 +52,13 @@ jobs: profile: pixel_xl script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: android-screen-recordings path: packages/react-native-editor/android-screen-recordings - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: appium-logs diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 6d1a127f05817..36dc3a5dc30c9 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -77,13 +77,13 @@ jobs: rm packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/main.jsbundle rm -rf packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/assets - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: ios-screen-recordings path: packages/react-native-editor/ios-screen-recordings - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: name: appium-logs diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 6665a1ef8f7b2..e0ce7283b28ba 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -29,7 +29,7 @@ jobs: run: npm run storybook:build - name: Deploy - uses: peaceiris/actions-gh-pages@068dc23d9710f1ba62e86896f84735d869951305 # v3.8.0 + uses: peaceiris/actions-gh-pages@de7ea6f8efb354206b205ef54722213d99067935 # v3.9.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./storybook/build diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index a8bdd00892e2e..6221dd30e5113 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -79,7 +79,7 @@ jobs: - name: Commit the Changelog update run: | git add changelog.txt - # Remove files that are not meant to be commited + # Remove files that are not meant to be committed # ie. release_notes.txt created on the previous step. git clean -fd # Only attempt to commit changelog if it has been modified. @@ -89,7 +89,7 @@ jobs: fi - name: Upload Changelog artifact - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 with: name: changelog ${{ matrix.label }} path: ./changelog.txt @@ -134,7 +134,7 @@ jobs: run: sed -i "s/${STABLE_TAG_PLACEHOLDER}/${STABLE_TAG}/g" ./trunk/readme.txt - name: Download Changelog Artifact - uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # v3.0.0 + uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1 with: name: changelog trunk path: trunk diff --git a/bin/packages/watch.js b/bin/packages/watch.js index ec5c504c0202f..8686aa82a588f 100644 --- a/bin/packages/watch.js +++ b/bin/packages/watch.js @@ -176,7 +176,7 @@ watch( PACKAGES_DIR, { recursive: true, delay: 500, filter: isWatchableFile }, ( event, filename ) => { - // Double check whether we're dealing with a file that needs watching, to accomodate for + // Double check whether we're dealing with a file that needs watching, to accommodate for // the inability to watch recursively on linux-based operating systems. if ( ! isSourceFile( filename ) || ! isModulePackage( filename ) ) { return; diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index c4a500b105c26..0571a0e72955f 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -193,6 +193,8 @@ async function runTestSuite( testSuite, performanceTestDirectory ) { * @param {WPPerformanceCommandOptions} options Command options. */ async function runPerformanceTests( branches, options ) { + const runningInCI = !! process.env.CI || !! options.ci; + // The default value doesn't work because commander provides an array. if ( branches.length === 0 ) { branches = [ 'trunk' ]; @@ -201,11 +203,11 @@ async function runPerformanceTests( branches, options ) { log( formats.title( '\n💃 Performance Tests 🕺\n' ), '\nWelcome! This tool runs the performance tests on multiple branches and displays a comparison table.\n' + - 'In order to run the tests, the tool is going to load a WordPress environment on 8888 and 8889 ports.\n' + + 'In order to run the tests, the tool is going to load a WordPress environment on ports 8888 and 8889.\n' + 'Make sure these ports are not used before continuing.\n' ); - if ( ! options.ci ) { + if ( ! runningInCI ) { await askForConfirmation( 'Ready to go? ' ); } diff --git a/changelog.txt b/changelog.txt index 33f0b72af73e6..5d7af6602e020 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,539 @@ == Changelog == += 14.5.0-rc.1 = + +## Changelog + +### Enhancements + +- Create Block: Update templates to use APIs introduced in WP 6.1. ([44185](https://github.com/WordPress/gutenberg/pull/44185)) +- Style Engine: Add support for dimensions.minHeight property. ([45334](https://github.com/WordPress/gutenberg/pull/45334)) + +#### Components +- Block mover button: Do not show focus styles on pointer interactions. ([45126](https://github.com/WordPress/gutenberg/pull/45126)) +- Border Controls: Update control components to allow 40px height. ([41860](https://github.com/WordPress/gutenberg/pull/41860)) +- BorderControl test: Await floating-ui state changes when rendering/opening popover. ([45241](https://github.com/WordPress/gutenberg/pull/45241)) +- Change color on destructive button focus state. ([44427](https://github.com/WordPress/gutenberg/pull/44427)) +- Convert the `ColorPalette` component to TypeScript. ([44632](https://github.com/WordPress/gutenberg/pull/44632)) +- NumberControl: Add custom spin buttons. ([45333](https://github.com/WordPress/gutenberg/pull/45333)) +- Remove unnecessary wrappers in stories. ([45305](https://github.com/WordPress/gutenberg/pull/45305)) +- SelectControl: Add onChange, onBlur and onFocus to storybook actions. ([45432](https://github.com/WordPress/gutenberg/pull/45432)) +- Update design of FontSizePicker when withSlider is set. ([44598](https://github.com/WordPress/gutenberg/pull/44598)) + +#### Block Library +- Comment template: Add spacing support. ([45101](https://github.com/WordPress/gutenberg/pull/45101)) +- Post comment count: Add spacing support. ([45150](https://github.com/WordPress/gutenberg/pull/45150)) +- Post comments form: Add spacing support. ([45091](https://github.com/WordPress/gutenberg/pull/45091)) + +#### Post Editor +- Edit Post: Improve distraction-free mode notices. ([45348](https://github.com/WordPress/gutenberg/pull/45348)) +- Move document information and outline to list view panel. ([44788](https://github.com/WordPress/gutenberg/pull/44788)) + +#### Patterns +- Add block pattern categories' descriptions to the REST API and update the descriptions. ([45244](https://github.com/WordPress/gutenberg/pull/45244)) + +#### Design Tools +- Spacing visualizer: Add option to trigger with mousover as well as value change. ([44955](https://github.com/WordPress/gutenberg/pull/44955)) + + +### Bug Fixes + +- Fix distraction free shortcut typo. ([45186](https://github.com/WordPress/gutenberg/pull/45186)) +- Fix resizeable editor scrolling. ([45189](https://github.com/WordPress/gutenberg/pull/45189)) +- Hide insertion point when moving out of the canvas. ([45420](https://github.com/WordPress/gutenberg/pull/45420)) +- I18n: Fix handling of nullish pot comments. ([45414](https://github.com/WordPress/gutenberg/pull/45414)) +- Prevent unexpected copying of the post title. ([41284](https://github.com/WordPress/gutenberg/pull/41284)) +- Typo in id in URL. ([45232](https://github.com/WordPress/gutenberg/pull/45232)) + +#### Components +- AutocompleteUI: Close popup when click happens outside of the popover. ([44795](https://github.com/WordPress/gutenberg/pull/44795)) +- Button component: Fix RTL alignment when containing icon and text. ([44787](https://github.com/WordPress/gutenberg/pull/44787)) +- ColorPalette: Fix transparent checkered background pattern. ([45295](https://github.com/WordPress/gutenberg/pull/45295)) +- Fix `UnitControl` disabled style is overridden by WP forms.css. ([45250](https://github.com/WordPress/gutenberg/pull/45250)) +- Fix popover deprecations. ([45195](https://github.com/WordPress/gutenberg/pull/45195)) +- InputControl: Allow inline styles to be applied to wrapper instead of inner input. ([45340](https://github.com/WordPress/gutenberg/pull/45340)) +- ItemGroup: Fix RTL text alignment when item is clickable. ([45280](https://github.com/WordPress/gutenberg/pull/45280)) +- Set AnglePickerControl Storybook Label Control type to text. ([45122](https://github.com/WordPress/gutenberg/pull/45122)) +- TabPanel: Add tests and changelog for onSelect behavior change. ([45211](https://github.com/WordPress/gutenberg/pull/45211)) +- Update ExternalLink to support onClick handler. ([45214](https://github.com/WordPress/gutenberg/pull/45214)) + +#### Block Library +- Allow direct selection of nested Page List block by avoiding dual rendering within block. ([45143](https://github.com/WordPress/gutenberg/pull/45143)) +- Fix Nav uncontrolled blocks saving/loading experience. ([45486](https://github.com/WordPress/gutenberg/pull/45486)) +- List v2: Fix migration when nested list is invalid. ([44822](https://github.com/WordPress/gutenberg/pull/44822)) +- Navigation block: Add padding to buttons when Submenus Open on click is enabled. ([44605](https://github.com/WordPress/gutenberg/pull/44605)) +- Site Tagline: Fix user permission HTTP errors. ([45140](https://github.com/WordPress/gutenberg/pull/45140)) +- Site Title: Avoid 403 errors for users with low permissions. ([45093](https://github.com/WordPress/gutenberg/pull/45093)) + +#### Design Tools +- Fix control widths for Cover, Search, and Spacer blocks. ([45329](https://github.com/WordPress/gutenberg/pull/45329)) +- SpacingSizesControl: Remove UnitControl inline style use. ([45412](https://github.com/WordPress/gutenberg/pull/45412)) + +#### Testing +- Fix uploading artifacts even when the tests are successful. ([45187](https://github.com/WordPress/gutenberg/pull/45187)) +- Migrate Gallery tests to Playwright. ([45202](https://github.com/WordPress/gutenberg/pull/45202)) + +#### Patterns +- Fix PHP warning in categories REST API controller. ([45410](https://github.com/WordPress/gutenberg/pull/45410)) + +#### Build Tooling +- ESLint: Exclude Playwright tests from testing library rules. ([45366](https://github.com/WordPress/gutenberg/pull/45366)) + +#### Layout +- Ensure block content is always returned as a string after processing. ([45330](https://github.com/WordPress/gutenberg/pull/45330)) + +#### Plugin +- Fix failing PHPUnit tests. ([45265](https://github.com/WordPress/gutenberg/pull/45265)) + +#### Post Editor +- Visual Editor: Fix permission error. ([45262](https://github.com/WordPress/gutenberg/pull/45262)) + +#### Global Styles +- Fix: Site editor clips body background style. ([45261](https://github.com/WordPress/gutenberg/pull/45261)) + +#### Meta Boxes +- Metaboxes save: Perform hasMetaBoxes check on every save. ([45145](https://github.com/WordPress/gutenberg/pull/45145)) + +#### Webfonts +- Web Font: Fix ascent/descent-override property typo. ([45125](https://github.com/WordPress/gutenberg/pull/45125)) + +#### Accessibility +- fix: Image caption supports Voice Control. ([44850](https://github.com/WordPress/gutenberg/pull/44850)) + +#### Document Settings +- PublishDateTimePicker: Retrieve all future posts in a given month. ([44540](https://github.com/WordPress/gutenberg/pull/44540)) + +#### Site Editor +- Only mark the 'Site' menu item active when editing a home template. ([42807](https://github.com/WordPress/gutenberg/pull/42807)) + + +### Performance + +- Lodash: Refactor `@wordpress/blocks` away from `_.includes()`. ([45311](https://github.com/WordPress/gutenberg/pull/45311)) +- Lodash: Refactor `@wordpress/keycodes` away from `_.includes()`. ([45306](https://github.com/WordPress/gutenberg/pull/45306)) +- Lodash: Refactor away from `_.kebabCase()` in site editor. ([45192](https://github.com/WordPress/gutenberg/pull/45192)) +- Lodash: Refactor block editor away from `_.some()`. ([45335](https://github.com/WordPress/gutenberg/pull/45335)) +- Lodash: Refactor blocks away from `_.some()`. ([45338](https://github.com/WordPress/gutenberg/pull/45338)) + +#### Post Editor +- Lodash: Refactor `@wordpress/edit-post` away from `_.includes()`. ([45310](https://github.com/WordPress/gutenberg/pull/45310)) +- Lodash: Refactor away from `_.castArray()`. ([45098](https://github.com/WordPress/gutenberg/pull/45098)) +- Lodash: Refactor away from `_.escape()`. ([45282](https://github.com/WordPress/gutenberg/pull/45282)) +- Lodash: Refactor away from `_.includes()`. ([45316](https://github.com/WordPress/gutenberg/pull/45316)) +- Lodash: Refactor editor away from `_.some()`. ([45342](https://github.com/WordPress/gutenberg/pull/45342)) + +#### Block Library +- Lodash: Refactor `@wordpress/block-library` away from `_.includes()`. ([45307](https://github.com/WordPress/gutenberg/pull/45307)) +- Lodash: Refactor away from a few similar `_.pickBy()`. ([45312](https://github.com/WordPress/gutenberg/pull/45312)) +- Lodash: Refactor block library away from `_.some()`. ([45337](https://github.com/WordPress/gutenberg/pull/45337)) + +#### Components +- Lodash: Refactor away from `_.some()`. ([45367](https://github.com/WordPress/gutenberg/pull/45367)) +- Lodash: Refactor away from `_.without()`. ([44980](https://github.com/WordPress/gutenberg/pull/44980)) + +#### Block Editor +- Lodash: Refactor away from `_.words()` - take 2. ([45281](https://github.com/WordPress/gutenberg/pull/45281)) + + +### Documentation + +- Add a readme to the letter spacing component. ([45308](https://github.com/WordPress/gutenberg/pull/45308)) +- Add changelogs for internal refactorings using inert. ([45269](https://github.com/WordPress/gutenberg/pull/45269)) +- Add storybook intro. ([45115](https://github.com/WordPress/gutenberg/pull/45115)) +- Character Swap. ([45355](https://github.com/WordPress/gutenberg/pull/45355)) +- Code Quality: Fix some misspelled words. ([45222](https://github.com/WordPress/gutenberg/pull/45222)) +- LinkControl: Use Popover new placement prop instead of legacy position prop in README. ([44388](https://github.com/WordPress/gutenberg/pull/44388)) +- Updating curation document to include content lock ability. ([44908](https://github.com/WordPress/gutenberg/pull/44908)) +- useAnchorRef: Update deprecation message. ([45302](https://github.com/WordPress/gutenberg/pull/45302)) + +#### Components +- Disabled: Update documentation to clarify the absence of inert polyfill. ([45272](https://github.com/WordPress/gutenberg/pull/45272)) + + +### Code Quality + +- Block Editor: Improve `MediaReplaceFlow` tests. ([45424](https://github.com/WordPress/gutenberg/pull/45424)) +- Block Editor: Refactor align tests to RTL. ([45152](https://github.com/WordPress/gutenberg/pull/45152)) +- Block switcher preview Popover: Use new placement prop instead of legacy position prop. ([44387](https://github.com/WordPress/gutenberg/pull/44387)) +- Button block Popover: Use placement prop instead of legacy position prop. ([44392](https://github.com/WordPress/gutenberg/pull/44392)) +- Fix indent and quote in dependbot.yml. ([45167](https://github.com/WordPress/gutenberg/pull/45167)) +- Fix: PHP 8.1 Deprecation - strncmp(): Passing null to parameter. ([44829](https://github.com/WordPress/gutenberg/pull/44829)) +- Format Toolbar Popover: Use new placement prop instead of legacy position prop. ([44389](https://github.com/WordPress/gutenberg/pull/44389)) +- Image format popover: Use placement prop instead of legacy position prop. ([44398](https://github.com/WordPress/gutenberg/pull/44398)) +- Link format Popover: Use placement prop instead of legacy position prop. ([44399](https://github.com/WordPress/gutenberg/pull/44399)) +- Navigation Link Popover: Use placement prop instead of legacy position prop. ([44394](https://github.com/WordPress/gutenberg/pull/44394)) +- Navigation Submenu Popover: Use placement prop instead of legacy position prop. ([44395](https://github.com/WordPress/gutenberg/pull/44395)) +- Replace the MainDashboardButton slot with a setting in the site editor. ([45149](https://github.com/WordPress/gutenberg/pull/45149)) +- Run script loader test. ([45288](https://github.com/WordPress/gutenberg/pull/45288)) +- Site Editor: Move the save view state to the edit site store. ([45200](https://github.com/WordPress/gutenberg/pull/45200)) +- Small follow-ups to the distraction free mode PR. ([45151](https://github.com/WordPress/gutenberg/pull/45151)) +- Tests: Use `container` instead of `container.firstChild` for snapshots. ([45278](https://github.com/WordPress/gutenberg/pull/45278)) +- URL Input Popover: Use new placement prop instead of legacy position prop. ([44390](https://github.com/WordPress/gutenberg/pull/44390)) +- useFocusOutside: Rewrite hook to TypeScript, rewrite tests to model RTL and user-event. ([45317](https://github.com/WordPress/gutenberg/pull/45317)) +- useFocusableIframe: Refactor to TypeScript. ([45428](https://github.com/WordPress/gutenberg/pull/45428)) + +#### Components +- Add a popover `variant` prop and refactor popovers to use it, deprecate `isAlternate`. ([45137](https://github.com/WordPress/gutenberg/pull/45137)) +- Add parseQuantityAndUnitFromRawValue tests. ([45260](https://github.com/WordPress/gutenberg/pull/45260)) +- Autocompleter Popover: Use placement prop instead of legacy position prop. ([44396](https://github.com/WordPress/gutenberg/pull/44396)) +- FontSizePicker: Rewrite unit tests to use userEvent and be more comprehensive. ([45298](https://github.com/WordPress/gutenberg/pull/45298)) +- Improve `BorderBoxControl` tests. ([45208](https://github.com/WordPress/gutenberg/pull/45208)) +- Refactor `ContextSystemProvider` and the `useUpdateEffect` util to ignore `exhaustive-deps`. ([45044](https://github.com/WordPress/gutenberg/pull/45044)) +- Refactor `SlotFill` to pass `exhaustive-deps`. ([44403](https://github.com/WordPress/gutenberg/pull/44403)) +- Refactor `Snackbar` to pass `exhaustive-deps`. ([44934](https://github.com/WordPress/gutenberg/pull/44934)) +- Refactor `TabPanel` to pass `exhaustive-deps`. ([44935](https://github.com/WordPress/gutenberg/pull/44935)) +- Remove unnecessary `.firstChild` from tests. ([45419](https://github.com/WordPress/gutenberg/pull/45419)) +- Update some React 18 related types. ([45279](https://github.com/WordPress/gutenberg/pull/45279)) + +#### Global Styles +- Add `wp_theme_has_theme_json` as a public API to know whether a theme has a `theme.json`. ([45168](https://github.com/WordPress/gutenberg/pull/45168)) +- Deprecate `WP_Theme_JSON_Resolver:Theme_has_support()`. ([45380](https://github.com/WordPress/gutenberg/pull/45380)) + +#### Post Editor +- Editors: Refactor icon tests to follow `no-container` rule. ([45422](https://github.com/WordPress/gutenberg/pull/45422)) + + +### Tools + +#### Testing +- Add end-to-end tests for Drag-and-Drop in the inserter. ([44631](https://github.com/WordPress/gutenberg/pull/44631)) +- Cleanup after the writing flow end-to-end tests. ([45119](https://github.com/WordPress/gutenberg/pull/45119)) +- Migrate Fullscreen mode test to Playwright. ([43963](https://github.com/WordPress/gutenberg/pull/43963)) +- Migrate New Default Post Content Test to Playwright. ([45267](https://github.com/WordPress/gutenberg/pull/45267)) +- Migrate Splitting Merging test to Playwright. ([44916](https://github.com/WordPress/gutenberg/pull/44916)) +- Migrate `site-editor-inserter` end-to-end test to Playwright. ([44507](https://github.com/WordPress/gutenberg/pull/44507)) +- Migrate `templates` end-to-end test to Playwright. ([45393](https://github.com/WordPress/gutenberg/pull/45393)) +- Migrate columns test case to Playwright. ([43964](https://github.com/WordPress/gutenberg/pull/43964)) +- Re-enable skipped Gallery block end-to-end test. ([45266](https://github.com/WordPress/gutenberg/pull/45266)) +- Try fixing Site Title flaky end-to-end tests. ([45160](https://github.com/WordPress/gutenberg/pull/45160)) +- Update Playwright to v1.27. ([45193](https://github.com/WordPress/gutenberg/pull/45193)) + +#### Build Tooling +- ESLint: Add and enable `eslint-plugin-testing-library`. ([45103](https://github.com/WordPress/gutenberg/pull/45103)) +- Remove comments from compiled styles. ([43177](https://github.com/WordPress/gutenberg/pull/43177)) +- Upgrade rtlcss to v4.0.0. ([43208](https://github.com/WordPress/gutenberg/pull/43208)) +- build: Native demo editor supports Xcode 14. ([45120](https://github.com/WordPress/gutenberg/pull/45120)) + + +### Various + +- Add :Visited to theme.json schema. ([45236](https://github.com/WordPress/gutenberg/pull/45236)) +- Add Playwright Compatibility-classic-editor Test. ([43979](https://github.com/WordPress/gutenberg/pull/43979)) +- Add rel attribute for Social Icons. ([45469](https://github.com/WordPress/gutenberg/pull/45469)) +- Add: Do not use in production to content locking experimental API's. ([45291](https://github.com/WordPress/gutenberg/pull/45291)) +- Migrate iframe-rendering test case. ([44535](https://github.com/WordPress/gutenberg/pull/44535)) +- Post comments link: Add spacing support. ([45184](https://github.com/WordPress/gutenberg/pull/45184)) +- PostTextEditor test: Wrap .blur calls in act(). ([45243](https://github.com/WordPress/gutenberg/pull/45243)) +- Revert "Lodash: Refactor away from `_.kebabCase()` in site editor". ([45206](https://github.com/WordPress/gutenberg/pull/45206)) + +#### Components +- Button: Refactor Storybook to controls and align documentation. ([44105](https://github.com/WordPress/gutenberg/pull/44105)) +- DateTimePicker: Add `__next*` props in Storybook. ([45164](https://github.com/WordPress/gutenberg/pull/45164)) +- Font Size Picker: Update changelog for #45041. ([45180](https://github.com/WordPress/gutenberg/pull/45180)) +- FormFileUpload: Remove unused story file. ([45286](https://github.com/WordPress/gutenberg/pull/45286)) +- RadioGroup: Mark as deprecated. ([45389](https://github.com/WordPress/gutenberg/pull/45389)) +- Revert "Navigator: Remove overflow styles from NavigatorScreen". ([45303](https://github.com/WordPress/gutenberg/pull/45303)) +- Storybook: Addon to wrap stories in max-width div. ([45134](https://github.com/WordPress/gutenberg/pull/45134)) +- TextControl: Set Storybook control types on help, label and type. ([45405](https://github.com/WordPress/gutenberg/pull/45405)) +- ToggleGroupControl: Add de-selectable variant. ([45123](https://github.com/WordPress/gutenberg/pull/45123)) +- ToggleGroupControl: Remove invalid props from types. ([45114](https://github.com/WordPress/gutenberg/pull/45114)) + +#### Block Library +- Gallery: Register styles with style engine. ([43070](https://github.com/WordPress/gutenberg/pull/43070)) +- Navigation block: Return undefined from useEffect. ([45239](https://github.com/WordPress/gutenberg/pull/45239)) +- Reset background-image property for outline button style. ([45234](https://github.com/WordPress/gutenberg/pull/45234)) +- [Audio]: Add toolbar button to add/remove caption. ([45112](https://github.com/WordPress/gutenberg/pull/45112)) +- [Video]: Add toolbar button to add/remove caption. ([45113](https://github.com/WordPress/gutenberg/pull/45113)) +- [Video]: Update tracks editor icon to text button. ([45245](https://github.com/WordPress/gutenberg/pull/45245)) + +#### Global Styles +- Embed: Add deprecation for the caption element. ([45166](https://github.com/WordPress/gutenberg/pull/45166)) +- File Block: Add a deprecation for the button element class name. ([45159](https://github.com/WordPress/gutenberg/pull/45159)) +- Gallery: Add a deprecation for captions in the gallery block. ([45173](https://github.com/WordPress/gutenberg/pull/45173)) +- Table Block: Add a deprecation for the figcaption element class name. ([45161](https://github.com/WordPress/gutenberg/pull/45161)) +- Video: Add a deprecation for the caption element. ([45169](https://github.com/WordPress/gutenberg/pull/45169)) + +#### Meta Boxes +- setAvailableMetaBoxesPerLocation: Merge new metaboxes into existing. ([45156](https://github.com/WordPress/gutenberg/pull/45156)) + +#### Post Editor +- hasChangedContent: Remove obsolete blocks check. ([45090](https://github.com/WordPress/gutenberg/pull/45090)) + +#### Block Editor +- Raw handling: Markdown: Also convert bulleted list with bullet characters. ([45017](https://github.com/WordPress/gutenberg/pull/45017)) + +#### Layout +- Try adding layout classnames to inner block wrapper. ([44600](https://github.com/WordPress/gutenberg/pull/44600)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @alvitazwar: Migrate iframe-rendering test case. ([44535](https://github.com/WordPress/gutenberg/pull/44535)) +- @GeoJunkie: PublishDateTimePicker: Retrieve all future posts in a given month. ([44540](https://github.com/WordPress/gutenberg/pull/44540)) +- @Initsogar: Components: Update ExternalLink to support onClick handler. ([45214](https://github.com/WordPress/gutenberg/pull/45214)) +- @jornp: Button component: Fix RTL alignment when containing icon and text. ([44787](https://github.com/WordPress/gutenberg/pull/44787)) +- @pkorzelius: Character Swap. ([45355](https://github.com/WordPress/gutenberg/pull/45355)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @alvitazwar @andrewserong @annezazu @aristath @BE-Webdesign @bph @brookewp @carolinan @chad1008 @ciampo @dcalhoun @ellatrix @fluiddot @GeoJunkie @georgeh @getdave @glendaviesnz @gziolo @Initsogar @jorgefilipecosta @jornp @jsnajdr @kevin940726 @KevinBatdorf @kienstra @Mamaduka @mikachan @mirka @noisysocks @ntsekouras @oandregal @pkorzelius @pooja-muchandikar @ramonjd @SavPhill @scruffian @SiobhyB @Soean @t-hamano @talldan @tellthemachines @tyxla @walbo @youknowriad + + += 14.4.0 = + + + +## Changelog + +### Enhancements + +- Add prop to disable block selection clearer in BlockList. ([44517](https://github.com/WordPress/gutenberg/pull/44517)) +- Multi-select: Fix 1px indent. ([44709](https://github.com/WordPress/gutenberg/pull/44709)) +- ServerSideRender: Add new `skipBlockSupportAttributes` prop. ([44491](https://github.com/WordPress/gutenberg/pull/44491)) +- Tag Processor: Add get_updated_html as a non-toString method of stringifying the markup. ([44597](https://github.com/WordPress/gutenberg/pull/44597)) +- Try: Add a small radius to the multi selection style. ([44708](https://github.com/WordPress/gutenberg/pull/44708)) +- Block Locking: Adds content locking to navigation block. ([44739](https://github.com/WordPress/gutenberg/pull/44739)) +- Rich Text: Use fallback icon for highlight format. ([44705](https://github.com/WordPress/gutenberg/pull/44705)) +- Create Block: Allows custom keys to be generated in block.json files and package.json files. ([44649](https://github.com/WordPress/gutenberg/pull/44649)) + +#### Block Library +- Comments: Add spacing support. ([45102](https://github.com/WordPress/gutenberg/pull/45102)) +- Tag Cloud: Add typography supports (except font size). ([43452](https://github.com/WordPress/gutenberg/pull/43452)) +- Image: Add toolbar button to add a caption. ([44965](https://github.com/WordPress/gutenberg/pull/44965)) +- List Item: Adopt typography supports. ([43312](https://github.com/WordPress/gutenberg/pull/43312)) + +#### Components +- BorderBoxControl: Omit unit select when values are mixed. ([44592](https://github.com/WordPress/gutenberg/pull/44592)) +- SuggestionList: Use requestAnimationFrame instead of setTimeout when scrolling selected item into view. ([44573](https://github.com/WordPress/gutenberg/pull/44573)) + + +#### Block Editor +- Introduce distraction free mode. ([41740](https://github.com/WordPress/gutenberg/pull/41740)) +- Redesign the main pattern inserter. ([44028](https://github.com/WordPress/gutenberg/pull/44028)) +- Inserter: Add a more pronounced hover effect. ([44711](https://github.com/WordPress/gutenberg/pull/44711)) + + +#### Design Tools +- SpacingSizesControl: Increase slider's max value to 300. ([44956](https://github.com/WordPress/gutenberg/pull/44956)) +- Style engine: Permit wp custom CSS properties. ([43071](https://github.com/WordPress/gutenberg/pull/43071)) +- Try color theming. ([44668](https://github.com/WordPress/gutenberg/pull/44668)) +- Global Styles: Modify Frame animation of styles grid. ([39717](https://github.com/WordPress/gutenberg/pull/39717)) + + + +### Bug Fixes + + +- Clickable placeholder upload button. ([44817](https://github.com/WordPress/gutenberg/pull/44817)) +- Insertion point showing up unexpectedly. ([44702](https://github.com/WordPress/gutenberg/pull/44702)) +- Overflowing patterns. ([44853](https://github.com/WordPress/gutenberg/pull/44853)) +- Inspector is usable on the top level block even if it is content locked. ([44878](https://github.com/WordPress/gutenberg/pull/44878)) +- Use active variation as the parent block if available. ([44609](https://github.com/WordPress/gutenberg/pull/44609)) +- Placeholder for the navigation link label, to be about the label. ([44733](https://github.com/WordPress/gutenberg/pull/44733)) +- Hide the inbetween inserter consistently as you move the mouse. ([44814](https://github.com/WordPress/gutenberg/pull/44814)) +- Margin visualiser: Apply negative value to margins with calc(). ([44718](https://github.com/WordPress/gutenberg/pull/44718)) +- Placeholder: Fix hover style. ([44701](https://github.com/WordPress/gutenberg/pull/44701)) +- Post editor: Rename view to Preview. ([45074](https://github.com/WordPress/gutenberg/pull/45074)) +- Prevent empty block toolbars from showing empty slots. ([44704](https://github.com/WordPress/gutenberg/pull/44704)) +- Use inert attribute instead of useDisabled. ([44865](https://github.com/WordPress/gutenberg/pull/44865)) +- cleanForSlug: Replace multiple hyphens with a single one. ([44873](https://github.com/WordPress/gutenberg/pull/44873)) +- Block Popover: Fix incorrect positioning of padding and margin visualizers on scroll. ([44998](https://github.com/WordPress/gutenberg/pull/44998)) +- Most used tags: Try fixing label. ([44859](https://github.com/WordPress/gutenberg/pull/44859)) +- Only include theme.css if the theme declares support for wp-block-styles. ([44640](https://github.com/WordPress/gutenberg/pull/44640)) +- Merge inner blocks if wrappers are equal. ([43181](https://github.com/WordPress/gutenberg/pull/43181)) +- Try nested patterns previews with block editor setting. ([44784](https://github.com/WordPress/gutenberg/pull/44784)) +- Design Tools Adjust the custom range steps to match the units chosen. ([44959](https://github.com/WordPress/gutenberg/pull/44959)) +- Global Styles: Invoke zoomed-out view when selecting a style variation. ([44987](https://github.com/WordPress/gutenberg/pull/44987)) +- Plugin: Don't use the custom 'Template Parts' page with WP 6.1 and above. ([45158](https://github.com/WordPress/gutenberg/pull/45158)) + + +#### Block Library +- Avoid querying block templates during installation. ([44584](https://github.com/WordPress/gutenberg/pull/44584)) +- Buttons: Add specificity for the editor. ([44731](https://github.com/WordPress/gutenberg/pull/44731)) +- Embed Block: Add support for Tumblr Dashboard URLs. ([44854](https://github.com/WordPress/gutenberg/pull/44854)) +- Fix list outdents on Enter in quote block. ([44809](https://github.com/WordPress/gutenberg/pull/44809)) +- Fix the cover block focal point picker. ([44991](https://github.com/WordPress/gutenberg/pull/44991)) +- Fix typo for word occurred. ([44914](https://github.com/WordPress/gutenberg/pull/44914)) +- Fix visibility of nested Group block appender. ([45050](https://github.com/WordPress/gutenberg/pull/45050)) +- Fix: Follow discussion settings in the comments block edit. ([44463](https://github.com/WordPress/gutenberg/pull/44463)) +- Group, Row, Stack, Columns. Fix missing border regression. ([44696](https://github.com/WordPress/gutenberg/pull/44696)) +- List Item: Allow Gutenberg to override core block type. ([44911](https://github.com/WordPress/gutenberg/pull/44911)) +- List v2: Selection when creating paragraph from empty list item. ([44864](https://github.com/WordPress/gutenberg/pull/44864)) +- Remove anchor support from the navigation block. ([44721](https://github.com/WordPress/gutenberg/pull/44721)) +- Removes __unstableMaxPages attribute from Page List block (and Nav block). ([44415](https://github.com/WordPress/gutenberg/pull/44415)) +- Site Logo: User permission HTTP errors. ([45104](https://github.com/WordPress/gutenberg/pull/45104)) +- Site logo: Centered state, for upload button. ([44861](https://github.com/WordPress/gutenberg/pull/44861)) +- Media: Cover block text color heuristic for cross origin media. ([44552](https://github.com/WordPress/gutenberg/pull/44552)) +- Icons: Arrow icons being misaligned. ([44666](https://github.com/WordPress/gutenberg/pull/44666)) +- Patterns: Hide list items from content area of content locked blocks. ([44676](https://github.com/WordPress/gutenberg/pull/44676)) +- Templates API: Avoid PHP warning when getting dynamic template data. ([44783](https://github.com/WordPress/gutenberg/pull/44783)) +- Block Settings: Show `move to` on nested blocks when only one root block. ([44827](https://github.com/WordPress/gutenberg/pull/44827)) +- Navigation: Fallback to a classic menu if one is available. ([44173](https://github.com/WordPress/gutenberg/pull/44173)) + +#### Site Editor +- Toggle Navigation Menus Sidebar. ([44860](https://github.com/WordPress/gutenberg/pull/44860)) +- Zoomed out view: Keep list view open when entering mode. ([44781](https://github.com/WordPress/gutenberg/pull/44781)) + + +#### Block Editor +- Cover: Fix erroneous focus style in editor. ([44707](https://github.com/WordPress/gutenberg/pull/44707)) +- Native inner blocks merge where appropriate. ([45048](https://github.com/WordPress/gutenberg/pull/45048)) + + +#### Typography +- Fluid typography: Convert server-side block support values. ([44762](https://github.com/WordPress/gutenberg/pull/44762)) +- Fluid typography: Covert font size number values to pixels. ([44807](https://github.com/WordPress/gutenberg/pull/44807)) +- Fluid typography: Ensure fontsizes are strings or integers. ([44847](https://github.com/WordPress/gutenberg/pull/44847)) +- Font Size Picker Hint: Fallback to font size `slug` if `name` is undefined. ([45041](https://github.com/WordPress/gutenberg/pull/45041)) +- Make custom font sizes appear fluid in the block editor when fluid typography is enabled. ([44765](https://github.com/WordPress/gutenberg/pull/44765)) +- Search block: Ensure font sizes values are converted to fluid in the editor. ([44852](https://github.com/WordPress/gutenberg/pull/44852)) +- Fluid typography: Convert font size inline style attributes to fluid values. ([44764](https://github.com/WordPress/gutenberg/pull/44764)) + +#### Components +- FontSizePicker: Fix header order in RTL languages. ([44590](https://github.com/WordPress/gutenberg/pull/44590)) +- Navigator: Restore focus only once per location. ([44972](https://github.com/WordPress/gutenberg/pull/44972)) +- Spacing Sizes Control: Try improving layout spacing. ([44858](https://github.com/WordPress/gutenberg/pull/44858)) + + +#### Global Styles +- Ensure style card effect doesn't cause scrollbars to appear. ([44823](https://github.com/WordPress/gutenberg/pull/44823)) +- Fluid Typography: Fix bug in global styles where fluid clamp rules were not calculated for custom values. ([44761](https://github.com/WordPress/gutenberg/pull/44761)) + +### Accessibility +- Fix the block toolbar styling when the 'Show button text labels' preference is enabled. ([44779](https://github.com/WordPress/gutenberg/pull/44779)) +- Fluid typography: Add font size constraints. ([44993](https://github.com/WordPress/gutenberg/pull/44993)) + + +### Performance + +- Lodash refactor away ([44892](https://github.com/WordPress/gutenberg/pull/44892), [45014](https://github.com/WordPress/gutenberg/pull/45014), [45010](https://github.com/WordPress/gutenberg/pull/45010), [44875](https://github.com/WordPress/gutenberg/pull/44875), [44901](https://github.com/WordPress/gutenberg/pull/44901), [44874](https://github.com/WordPress/gutenberg/pull/44874), [44894](https://github.com/WordPress/gutenberg/pull/44894)) +- Lodash: Remove completely from `@wordpress/viewport` package. ([44572](https://github.com/WordPress/gutenberg/pull/44572)) +- Remove unnecessary repeated function call. ([44545](https://github.com/WordPress/gutenberg/pull/44545)) +- Block Library, QueryInspectorControls: Avoid rerender of TaxonomyControls on every keystroke. ([44562](https://github.com/WordPress/gutenberg/pull/44562)) + +### Documentation + +- Fix broken link to `01-basic-esnext` example. ([45000](https://github.com/WordPress/gutenberg/pull/45000)) +- Add `ariaLabel` support to block supports reference documentation. ([45006](https://github.com/WordPress/gutenberg/pull/45006)) +- Docs: Fix some typos. ([44713](https://github.com/WordPress/gutenberg/pull/44713)) +- Layout Docs: Fix broken link in table of contents. ([44719](https://github.com/WordPress/gutenberg/pull/44719)) +- Layout: Document the current state of the layout block support. ([42619](https://github.com/WordPress/gutenberg/pull/42619)) +- Make `spacingScale` description more accurate in theme.json schema. ([44794](https://github.com/WordPress/gutenberg/pull/44794)) +- Remove duplicate img tag. ([44936](https://github.com/WordPress/gutenberg/pull/44936)) +- Updating versions in WP in light of 6.0.3 release. ([45052](https://github.com/WordPress/gutenberg/pull/45052)) +- package(prettier-config): Update documentation. ([44620](https://github.com/WordPress/gutenberg/pull/44620)) +- Connect extend Query Loop documentation. ([44699](https://github.com/WordPress/gutenberg/pull/44699)) +- Update Extending the Query Loop block documentation. ([44703](https://github.com/WordPress/gutenberg/pull/44703)) +- Update link to plugin-sidebar example in documentation. ([44790](https://github.com/WordPress/gutenberg/pull/44790)) +- Update versions in WordPress documentation for 6.1. ([44785](https://github.com/WordPress/gutenberg/pull/44785)) +- Update visually hidden documentation to mention stacking context. ([44867](https://github.com/WordPress/gutenberg/pull/44867)) + + + +### Code Quality + +- Fix comment in edit post reducer. ([44978](https://github.com/WordPress/gutenberg/pull/44978)) +- Fix: PHP 8.1 Deprecation messages. ([44828](https://github.com/WordPress/gutenberg/pull/44828)) +- Navigator: Remove overflow styles from NavigatorScreen. ([44973](https://github.com/WordPress/gutenberg/pull/44973)) +- Refactor the document actions component. ([45060](https://github.com/WordPress/gutenberg/pull/45060)) +- Simplify BlockStyles preview by removing the slot. ([44825](https://github.com/WordPress/gutenberg/pull/44825)) +- Update 6.1 CSS filter to match core. ([44962](https://github.com/WordPress/gutenberg/pull/44962)) +- Fluid Typography: Add missing style.css file to test theme. ([44958](https://github.com/WordPress/gutenberg/pull/44958)) +- Site Editor: Dynamic Templates Names and Descriptions: Code parity with wp/6.1. ([44646](https://github.com/WordPress/gutenberg/pull/44646)) +- Fluid Typography: Use wp_ prefixed function. ([44876](https://github.com/WordPress/gutenberg/pull/44876)) +- Remove gutenberg_ prefix for theme_json_get_style_nodes filter. ([44949](https://github.com/WordPress/gutenberg/pull/44949)) +- Update the names of filters for global styles data. ([44940](https://github.com/WordPress/gutenberg/pull/44940)) +- Try: Refresh selection styles. ([44150](https://github.com/WordPress/gutenberg/pull/44150)) +- Design Tools, Archive Block: Prevent spacing styles and additional CSS classes from being printed twice. ([44438](https://github.com/WordPress/gutenberg/pull/44438)) +- Design Tools, Tag Cloud Block: Prevent block support styles and additional CSS classes from being printed twice. ([44439](https://github.com/WordPress/gutenberg/pull/44439)) +- Site Editor: Rename header and sidebar components in edit-site. ([44974](https://github.com/WordPress/gutenberg/pull/44974)) +- Refactor dropzone for unmodified default blocks with `useBlockDropZone` and `InsertionPoint`. ([44647](https://github.com/WordPress/gutenberg/pull/44647)) +- Data: Cleanup `useDispatch` tests. ([44856](https://github.com/WordPress/gutenberg/pull/44856)) + + +#### ESLint +- ESLint: Fix rule violations. ([44927](https://github.com/WordPress/gutenberg/pull/44927), [44896](https://github.com/WordPress/gutenberg/pull/44896), [44895](https://github.com/WordPress/gutenberg/pull/44895), [44926](https://github.com/WordPress/gutenberg/pull/44926), [44939](https://github.com/WordPress/gutenberg/pull/44939), [44930](https://github.com/WordPress/gutenberg/pull/44930), [44902](https://github.com/WordPress/gutenberg/pull/44902), [44928](https://github.com/WordPress/gutenberg/pull/44928), [44925](https://github.com/WordPress/gutenberg/pull/44925), [45061](https://github.com/WordPress/gutenberg/pull/45061), [45067](https://github.com/WordPress/gutenberg/pull/45067), [45097](https://github.com/WordPress/gutenberg/pull/45097), [45064](https://github.com/WordPress/gutenberg/pull/45064), [45068](https://github.com/WordPress/gutenberg/pull/45068), [45062](https://github.com/WordPress/gutenberg/pull/45062), [45096](https://github.com/WordPress/gutenberg/pull/45096), [45106](https://github.com/WordPress/gutenberg/pull/45106), [45018](https://github.com/WordPress/gutenberg/pull/45018)) +- ESLint: Fix a few jsdoc/check-line-alignment warnings. ([44984](https://github.com/WordPress/gutenberg/pull/44984)) +- ESLint: Fix empty `toHaveTextContent`. ([45007](https://github.com/WordPress/gutenberg/pull/45007)) + +#### Refactor tests to RTL +- Data Layer ([44802](https://github.com/WordPress/gutenberg/pull/44802), [44855](https://github.com/WordPress/gutenberg/pull/44855), [45058](https://github.com/WordPress/gutenberg/pull/45058)) +- Block Editor ([44869](https://github.com/WordPress/gutenberg/pull/44869), [44870](https://github.com/WordPress/gutenberg/pull/44870), [44871](https://github.com/WordPress/gutenberg/pull/44871), [45071](https://github.com/WordPress/gutenberg/pull/45071)) +- Components ([45072](https://github.com/WordPress/gutenberg/pull/45072), [45012](https://github.com/WordPress/gutenberg/pull/45012), [44801](https://github.com/WordPress/gutenberg/pull/44801), [44826](https://github.com/WordPress/gutenberg/pull/44826), [44824](https://github.com/WordPress/gutenberg/pull/44824), [44821](https://github.com/WordPress/gutenberg/pull/44821), [44820](https://github.com/WordPress/gutenberg/pull/44820)) +- Post Editor([45011](https://github.com/WordPress/gutenberg/pull/45011), [44835](https://github.com/WordPress/gutenberg/pull/44835), [45066](https://github.com/WordPress/gutenberg/pull/45066), [44923](https://github.com/WordPress/gutenberg/pull/44923), [44872](https://github.com/WordPress/gutenberg/pull/44872)) +- Compose ([44818](https://github.com/WordPress/gutenberg/pull/44818), [44819](https://github.com/WordPress/gutenberg/pull/44819), [44912](https://github.com/WordPress/gutenberg/pull/44912), [45108](https://github.com/WordPress/gutenberg/pull/45108)) +- Viewport ([44766](https://github.com/WordPress/gutenberg/pull/44766), [44769](https://github.com/WordPress/gutenberg/pull/44769)) +- Element ([44804](https://github.com/WordPress/gutenberg/pull/44804)) +- Interface ([44803](https://github.com/WordPress/gutenberg/pull/44803)) + + +#### Components +- Navigator: Refactor Storybook code to TypeScript and controls. ([44979](https://github.com/WordPress/gutenberg/pull/44979)) +- Navigator: Refactor tests to TypeScript and user-event. ([44970](https://github.com/WordPress/gutenberg/pull/44970)) +- Refactor `ToolsPanel` to pass `exhaustive-deps`. ([45028](https://github.com/WordPress/gutenberg/pull/45028)) +- Refactor `Tooltip` tests to always render inline. ([45105](https://github.com/WordPress/gutenberg/pull/45105)) +- Refactor to use `@testing-library/react`. ([44698](https://github.com/WordPress/gutenberg/pull/44698), [44695](https://github.com/WordPress/gutenberg/pull/44695), [44697](https://github.com/WordPress/gutenberg/pull/44697)) +- Refactor `Tooltip` to ignore `exhaustive-deps`. ([45043](https://github.com/WordPress/gutenberg/pull/45043)) +- Modal: Convert component to TypeScript. ([42949](https://github.com/WordPress/gutenberg/pull/42949)) +- Sandbox: Use toString to create observe and resize script string. ([42872](https://github.com/WordPress/gutenberg/pull/42872)) + + + +### Tools + +- Combine stale issue gardening workflows into one. ([44754](https://github.com/WordPress/gutenberg/pull/44754)) +- Ensure stale github actions workflow only runs for particular issue labels. ([44910](https://github.com/WordPress/gutenberg/pull/44910)) +- Configure Dependabot pull requests for GitHub Actions. ([44677](https://github.com/WordPress/gutenberg/pull/44677)) +- Storybook: Add theme switcher tool. ([44715](https://github.com/WordPress/gutenberg/pull/44715)) +- Bug Fix, wp-env: Use case insensitive regex when checking WP version string. ([44887](https://github.com/WordPress/gutenberg/pull/44887)) +- Cherry Pick Script: Restore retrieval of merge_commit_sha. ([44890](https://github.com/WordPress/gutenberg/pull/44890)) + +#### Schemas + +- Fix: Typo in block.json schema default scope values. ([44944](https://github.com/WordPress/gutenberg/pull/44944)) +- Fix: typo in block.json schema. ([44798](https://github.com/WordPress/gutenberg/pull/44798)) +- Fix: theme json schema validation. ([44799](https://github.com/WordPress/gutenberg/pull/44799)) +- Add title property to theme.json schema. ([44797](https://github.com/WordPress/gutenberg/pull/44797)) + +#### Build Tooling +- Add `.eslintcache` to `.gitignore`. ([44938](https://github.com/WordPress/gutenberg/pull/44938)) +- ESLint: Add and enable `eslint-plugin-jest-dom`. ([44983](https://github.com/WordPress/gutenberg/pull/44983)) +- Style Engine: Move PHP unit tests to Gutenberg. ([44722](https://github.com/WordPress/gutenberg/pull/44722)) + +#### Testing +- Add support for React 18 and later to jest-preset-default. ([44680](https://github.com/WordPress/gutenberg/pull/44680)) +- Migrate `nonce.test.js` to Playwright. ([44929](https://github.com/WordPress/gutenberg/pull/44929)) +- end-to-end Tests: Make attributes and innerblocks optional in insertBlock. ([44933](https://github.com/WordPress/gutenberg/pull/44933)) +- Bug Fix, end-to-end tests: Update button selector. ([45087](https://github.com/WordPress/gutenberg/pull/45087)) +- Mobile + - Code block - Migrate from React Test Render to React Native Testing Library. ([44833](https://github.com/WordPress/gutenberg/pull/44833)) + - Tooltip - Migrate from React Test Render to React Native Testing Library. ([44831](https://github.com/WordPress/gutenberg/pull/44831)) + - Update editor initial HTML test for iOS and update Appium. ([44732](https://github.com/WordPress/gutenberg/pull/44732)) + - useResizeObserver - Migrate from React Test Render to React Native Testing Library. ([44832](https://github.com/WordPress/gutenberg/pull/44832)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @benridane: Fix list outdents on Enter in quote block. ([44809](https://github.com/WordPress/gutenberg/pull/44809)) +- @jeremylind: FIX: Typo in block.json schema default scope values. ([44944](https://github.com/WordPress/gutenberg/pull/44944)) +- @PooSham: Add support for React 18 and later to jest-preset-default. ([44680](https://github.com/WordPress/gutenberg/pull/44680)) +- @thelovekesh: package(prettier-config): Update documentation. ([44620](https://github.com/WordPress/gutenberg/pull/44620)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamziel @afercia @ajlende @alanjacobmathew @amustaque97 @andrewserong @annezazu @BE-Webdesign @benridane @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @costdev @danielbachhuber @dcalhoun @desrosj @dinhtungdu @draganescu @ellatrix @fabiankaegy @fluiddot @geriux @getdave @glendaviesnz @gvgvgvijayan @jasmussen @jeremylind @jorgefilipecosta @joshuatf @jrfnl @jsnajdr @kevin940726 @Mamaduka @mikachan @mirka @mtias @noisysocks @ntsekouras @oandregal @ockham @pento @PooSham @ramonjd @ryanwelcher @sabernhardt @SantosGuillamot @scruffian @Soean @t-hamano @talldan @tellthemachines @thelovekesh @tyxla @walbo @youknowriad + + = 14.3.1 = diff --git a/docs/contributors/code/testing-overview.md b/docs/contributors/code/testing-overview.md index fa17909b4cd37..f68469f6a893f 100644 --- a/docs/contributors/code/testing-overview.md +++ b/docs/contributors/code/testing-overview.md @@ -367,13 +367,13 @@ describe( 'SolarSystem', () => { test( 'should render', () => { const { container } = render( ); - expect( container.firstChild ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); } ); test( 'should contain mars if planets is true', () => { const { container } = render( ); - expect( container.firstChild ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); expect( screen.getByText( /mars/i ) ).toBeInTheDocument(); } ); } ); @@ -422,7 +422,7 @@ test( 'should contain mars if planets is true', () => { const { container } = render( ); // Snapshot will catch unintended changes - expect( container.firstChild ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); // This is what we actually expect to find in our test expect( screen.getByText( /mars/i ) ).toBeInTheDocument(); @@ -447,8 +447,8 @@ Similarly, the `toMatchStyleDiffSnapshot` function allows to snapshot only the d test( 'should render margin', () => { const { container: spacer } = render( ); const { container: spacerWithMargin } = render( ); - expect( spacerWithMargin.firstChild ).toMatchStyleDiffSnapshot( - spacer.firstChild + expect( spacerWithMargin ).toMatchStyleDiffSnapshot( + spacer ); } ); ``` diff --git a/docs/explanations/faq.md b/docs/explanations/faq.md index b1eb22b330964..c56dd2e0c3c6c 100644 --- a/docs/explanations/faq.md +++ b/docs/explanations/faq.md @@ -28,7 +28,7 @@ What follows is a set of questions that have come up from the last few years of ### The Development Experience - [How do I make my own block?](#how-do-i-make-my-own-block) -- [Does Gutenberg involve editing posts/pages in the front-end?](#does-gutenberg-involve-editing-postspages-in-the-front-end) +- [Does Gutenberg involve editing posts/pages in the front-end?](#does-gutenberg-involve-editing-posts-pages-in-the-front-end) - [Given Gutenberg is built in JavaScript, how do old meta boxes (PHP) work?](#given-gutenberg-is-built-in-javascript-how-do-old-meta-boxes-php-work) - [How can plugins extend the Gutenberg UI?](#how-can-plugins-extend-the-gutenberg-ui) - [Are Custom Post Types still supported?](#are-custom-post-types-still-supported) diff --git a/docs/how-to-guides/curating-the-editor-experience.md b/docs/how-to-guides/curating-the-editor-experience.md index 4c7ef0096d051..584e6dc18b8a0 100644 --- a/docs/how-to-guides/curating-the-editor-experience.md +++ b/docs/how-to-guides/curating-the-editor-experience.md @@ -14,10 +14,25 @@ Users have the ability to lock and unlock blocks via the editor. The locking UI Keep in mind that each block you want to lock will need to be individually locked as desired. There is not a way to mass lock blocks currently. -**Lock patterns or templates** +**Apply block locking to patterns or templates** When building patterns or templates, theme authors can use these same UI tools to set the default locked state of blocks. For example, a theme author could lock various pieces of a header. Keep in mind that by default, users with editing access can unlock these blocks. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways and here’s more context on [creating a template with locked blocks](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/). You can build these patterns in the editor itself, including adding locking options, before following the [documentation to register them](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/). +**Apply content only editing in patterns or templates** + +This functionality was introduced in WordPress 6.1. In contrast to block locking, which disables the ability to move or remove blocks, content only editing is both designed for use at the pattern or template level and hides all design tools, while still allowing for the ability to edit the content of the blocks. This provides a great way to simplify the interface for users and preserve a design. When this option is added, the following changes occur: + +- Non-content child blocks (containers, spacers, columns, etc) are hidden from list view, un-clickable on the canvas, and entirely un-editable. +- The Inspector will display a list of all child 'content' blocks. Clicking a block in this list reveals its settings panel. +- The main List View only shows content blocks, all at the same level regardless of actual nesting. +- Children blocks within the overall content locked container are automatically move / remove locked. +- Additional child blocks cannot be inserted, further preserving the design and layout. +- There is a link in the block toolbar to ‘Modify’ that a user can toggle on/off to have access to the broader design tools. Currently, it's not possibly to programmatically remove this option. + +This option can be applied to Columns, Cover, and Group blocks as well as third-party blocks that have the templateLock attribute in its block.json. To adopt this functionality, you need to use `"templateLock":"contentOnly"`. [Here's an example of a pattern](https://gist.github.com/annezazu/d62acd2514cea558be6cea97fe28ff3c) with this functionality in place. For more information, please [review the relevant documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-templates/#locking). + +Note: There is no UI in place to manage content locking and it must be managed at the code level. + **Change permissions to control locking ability** Agencies and plugin authors can offer an even more curated experience by limiting which users have [permission to lock and unlock blocks](https://make.wordpress.org/core/2022/05/05/block-locking-settings-in-wordpress-6-0/). By default, anyone who is an administrator will have access to lock and unlock blocks. diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index 6ce54d8009dd7..6185043353fc2 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -13,7 +13,7 @@ Starting in WordPress 5.8 release, we encourage using the `block.json` metadata "category": "text", "parent": [ "core/group" ], "icon": "star", - "description": "Shows warning, error or success notices…", + "description": "Shows warning, error or success notices...", "keywords": [ "alert", "message" ], "version": "1.0.3", "textdomain": "my-plugin", @@ -646,7 +646,7 @@ return array( Starting in the WordPress 5.8 release, it is possible to instruct WordPress to enqueue scripts and styles for a block type only when rendered on the frontend. It applies to the following asset fields in the `block.json` file: - `script` -- `viewScript` (when the block defines `render_callback` during registration in PHP or a `render` field in its `block.json`, then the script is registered but the block author is responsible for enqueuing it) +- `viewScript` - `style` ## Internationalization diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 6d864b69d8bef..dd9fdbdc0566b 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -167,7 +167,7 @@ Contains the block elements used to display a comment, like the title, date, aut - **Name:** core/comment-template - **Category:** design -- **Supports:** align, typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Supports:** align, spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Comments @@ -491,7 +491,7 @@ Display a post's comments count. ([Source](https://github.com/WordPress/gutenber - **Name:** core/post-comments-count - **Category:** theme -- **Supports:** color (background, gradients, text), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** textAlign ## Post Comments Form @@ -500,7 +500,7 @@ Display a post's comments form. ([Source](https://github.com/WordPress/gutenberg - **Name:** core/post-comments-form - **Category:** theme -- **Supports:** color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** textAlign ## Post Comments Link @@ -509,7 +509,7 @@ Displays the link to the current post comments. ([Source](https://github.com/Wor - **Name:** core/post-comments-link - **Category:** theme -- **Supports:** color (background, link, ~~text~~), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** color (background, link, ~~text~~), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** textAlign ## Post Content @@ -753,7 +753,7 @@ Display an icon linking to a social media profile or site. ([Source](https://git - **Name:** core/social-link - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** label, service, url +- **Attributes:** label, rel, service, url ## Social Icons diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index 26337e973bd34..1c082528485c3 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -433,8 +433,7 @@ Update a metabox. ### setAvailableMetaBoxesPerLocation -Returns an action object used in signaling -what Meta boxes are available in which location. +Stores info about which Meta boxes are available in which location. _Parameters_ diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 573d092472521..9ee5fea71801b 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -189,6 +189,18 @@ _Returns_ - `boolean`: True if the navigation panel should be open; false if closed. +### isSaveViewOpened + +Returns the current opened/closed state of the save panel. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: True if the save panel should be open; false if closed. + ## Actions @@ -284,6 +296,14 @@ _Parameters_ - _isOpen_ `boolean`: If true, opens the nav panel. If false, closes it. It does not toggle the state, but sets it directly. +### setIsSaveViewOpened + +Sets whether the save view panel should be open. + +_Parameters_ + +- _isOpen_ `boolean`: If true, opens the save view. If false, closes it. It does not toggle the state, but sets it directly. + ### setNavigationPanelActiveMenu Action that sets the active navigation panel menu. diff --git a/gutenberg.php b/gutenberg.php index 9791f13a9a0a8..ab8a779c4622b 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.4.0-rc.1 + * Version: 14.5.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 15a7a131f8a9b..a50e1fb883717 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -291,6 +291,23 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support return ''; } +/** + * Gets classname from last tag in a string of HTML. + * + * @param string $html markup to be processed. + * @return string String of inner wrapper classnames. + */ +function gutenberg_get_classnames_from_last_tag( $html ) { + $tags = new WP_HTML_Tag_Processor( $html ); + $last_classnames = ''; + + while ( $tags->next_tag() ) { + $last_classnames = $tags->get_attribute( 'class' ); + } + + return (string) $last_classnames; +} + /** * Renders the layout config to the block wrapper. * @@ -320,7 +337,6 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $class_names = array(); $layout_definitions = _wp_array_get( $global_layout_settings, array( 'definitions' ), array() ); - $block_classname = wp_get_block_default_classname( $block['blockName'] ); $container_class = wp_unique_id( 'wp-container-' ); $layout_classname = ''; @@ -397,7 +413,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' ); $style = gutenberg_get_layout_style( - ".$block_classname.$container_class", + ".$container_class.$container_class", $used_layout, $has_block_gap_support, $gap_value, @@ -412,18 +428,24 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } } - /* - * This assumes the hook only applies to blocks with a single wrapper. - * A limitation of this hook is that nested inner blocks wrappers are not yet supported. - */ - $content = preg_replace( - '/' . preg_quote( 'class="', '/' ) . '/', - 'class="' . esc_attr( implode( ' ', $class_names ) ) . ' ', - $block_content, - 1 - ); + /** + * The first chunk of innerContent contains the block markup up until the inner blocks start. + * We want to target the opening tag of the inner blocks wrapper, which is the last tag in that chunk. + */ + $inner_content_classnames = isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) ? gutenberg_get_classnames_from_last_tag( $block['innerContent'][0] ) : ''; + + $content = new WP_HTML_Tag_Processor( $block_content ); + if ( $inner_content_classnames ) { + $content->next_tag( array( 'class_name' => $inner_content_classnames ) ); + foreach ( $class_names as $class_name ) { + $content->add_class( $class_name ); + } + } else { + $content->next_tag(); + $content->add_class( implode( ' ', $class_names ) ); + } - return $content; + return (string) $content; } // Register the block support. (overrides core one). @@ -433,6 +455,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { 'register_attribute' => 'gutenberg_register_layout_support', ) ); + if ( function_exists( 'wp_render_layout_support_flag' ) ) { remove_filter( 'render_block', 'wp_render_layout_support_flag' ); } @@ -454,7 +477,7 @@ function gutenberg_restore_group_inner_container( $block_content, $block ) { preg_quote( $tag_name, '/' ) ); if ( - WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() || + wp_theme_has_theme_json() || 1 === preg_match( $group_with_inner_container_regex, $block_content ) || ( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] ) ) { @@ -519,7 +542,7 @@ function gutenberg_restore_image_outer_container( $block_content, $block ) { )/iUx"; if ( - WP_Theme_JSON_Resolver::theme_has_support() || + wp_theme_has_theme_json() || 0 === preg_match( $image_with_align, $block_content, $matches ) ) { return $block_content; diff --git a/lib/client-assets.php b/lib/client-assets.php index f1b94c0d46abf..ea199750fa10b 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -318,7 +318,7 @@ function gutenberg_register_packages_styles( $styles ) { ); // Only load the default layout and margin styles for themes without theme.json file. - if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { $wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles'; } diff --git a/lib/compat/wordpress-6.0/block-patterns.php b/lib/compat/wordpress-6.0/block-patterns.php index 6ba5a15035530..c42ec73152219 100644 --- a/lib/compat/wordpress-6.0/block-patterns.php +++ b/lib/compat/wordpress-6.0/block-patterns.php @@ -15,7 +15,7 @@ function _register_remote_theme_patterns() { return; } - if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { return; } diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php index b85e8e335037a..45779d395e629 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php @@ -86,7 +86,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() * and merge the static::$theme upon that. */ $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() ); - if ( ! static::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { if ( ! isset( $theme_support_data['settings']['color'] ) ) { $theme_support_data['settings']['color'] = array(); } diff --git a/lib/compat/wordpress-6.0/client-assets.php b/lib/compat/wordpress-6.0/client-assets.php index 0c5697817a94a..f9affb31af4b5 100644 --- a/lib/compat/wordpress-6.0/client-assets.php +++ b/lib/compat/wordpress-6.0/client-assets.php @@ -105,6 +105,11 @@ function gutenberg_resolve_assets() { add_filter( 'block_editor_settings_all', function( $settings ) { + // The `__unstableResolvedAssets` are generated by `_wp_get_iframed_editor_assets` for WP >= 6.0. + if ( function_exists( '_wp_get_iframed_editor_assets' ) ) { + return $settings; + } + // In the future we can allow WP Dependency handles to be passed. $settings['__unstableResolvedAssets'] = gutenberg_resolve_assets(); return $settings; 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 1dfafe9f7631d..9ed3e891182cb 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 @@ -85,7 +85,7 @@ function gutenberg_get_global_styles_svg_filters() { } } - $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); + $supports_theme_json = wp_theme_has_theme_json(); $origins = array( 'default', 'theme', 'custom' ); if ( ! $supports_theme_json ) { diff --git a/lib/compat/wordpress-6.0/rest-api.php b/lib/compat/wordpress-6.0/rest-api.php index d8506911d18d1..0cf08533e33b7 100644 --- a/lib/compat/wordpress-6.0/rest-api.php +++ b/lib/compat/wordpress-6.0/rest-api.php @@ -35,14 +35,6 @@ function gutenberg_register_edit_site_export_endpoint() { } add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_endpoint' ); -/** - * Registers the block pattern categories REST API routes. - */ -function gutenberg_register_rest_block_pattern_categories() { - $block_patterns = new WP_REST_Block_Pattern_Categories_Controller(); - $block_patterns->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' ); /** * Register a core site settings. diff --git a/lib/compat/wordpress-6.1/block-editor-settings.php b/lib/compat/wordpress-6.1/block-editor-settings.php index 3181329475df4..cafe91e787dc6 100644 --- a/lib/compat/wordpress-6.1/block-editor-settings.php +++ b/lib/compat/wordpress-6.1/block-editor-settings.php @@ -68,7 +68,7 @@ function gutenberg_get_block_editor_settings( $settings ) { } } - if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( wp_theme_has_theme_json() ) { $block_classes = array( 'css' => 'styles', '__unstableType' => 'theme', diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index f06ecc93713f8..8e12d99a07490 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -1090,7 +1090,7 @@ protected static function compute_style_properties( $styles, $settings = array() * @param array $styles Styles subtree. * @param array $path Which property to process. * @param array $theme_json Theme JSON array. - * @return string Style property value. + * @return string|array|null Style property value. */ protected static function get_property_value( $styles, $path, $theme_json = null ) { $value = _wp_array_get( $styles, $path ); @@ -1113,7 +1113,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null } } - if ( is_array( $value ) ) { + if ( ! $value || is_array( $value ) ) { return $value; } 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 1837aa04dbb20..35c540ce1c57a 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 @@ -83,7 +83,7 @@ function gutenberg_get_global_stylesheet( $types = array() ) { } } $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); + $supports_theme_json = wp_theme_has_theme_json(); if ( empty( $types ) && ! $supports_theme_json ) { $types = array( 'variables', 'presets', 'base-layout-styles' ); } elseif ( empty( $types ) ) { diff --git a/lib/compat/wordpress-6.1/script-loader.php b/lib/compat/wordpress-6.1/script-loader.php index 0e27c62a1bda5..2bb9edd2a204d 100644 --- a/lib/compat/wordpress-6.1/script-loader.php +++ b/lib/compat/wordpress-6.1/script-loader.php @@ -5,37 +5,6 @@ * @package gutenberg */ -/** - * This function takes care of adding inline styles - * in the proper place, depending on the theme in use. - * - * This method was added to core in 5.9.1, but with a single param ($style). The second param ($priority) was - * added post 6.0, so the 6.1 release needs to have wp_enqueue_block_support_styles updated to include this param. - * - * For block themes, it's loaded in the head. - * For classic ones, it's loaded in the body - * because the wp_head action happens before - * the render_block. - * - * @link https://core.trac.wordpress.org/ticket/53494. - * - * @param string $style String containing the CSS styles to be added. - * @param int $priority To set the priority for the add_action. - */ -function gutenberg_enqueue_block_support_styles( $style, $priority = 10 ) { - $action_hook_name = 'wp_footer'; - if ( wp_is_block_theme() ) { - $action_hook_name = 'wp_head'; - } - add_action( - $action_hook_name, - static function () use ( $style ) { - echo "\n"; - }, - $priority - ); -} - /** * This applies a filter to the list of style nodes that comes from `get_style_nodes` in WP_Theme_JSON. * This particular filter removes all of the blocks from the array. diff --git a/lib/compat/wordpress-6.1/template-parts-screen.php b/lib/compat/wordpress-6.1/template-parts-screen.php index 93b68436764b3..c8b5958bedcd4 100644 --- a/lib/compat/wordpress-6.1/template-parts-screen.php +++ b/lib/compat/wordpress-6.1/template-parts-screen.php @@ -24,6 +24,24 @@ function gutenberg_template_parts_screen_menu() { return; } + global $submenu; + if ( ! isset( $submenu['themes.php'] ) ) { + return; + } + + $needs_custom_page = true; + foreach ( $submenu['themes.php'] as $menu_item ) { + if ( str_contains( $menu_item[2], 'site-editor.php?postType=wp_template_part' ) ) { + $needs_custom_page = false; + break; + } + } + + // Don't use the custom 'Template Parts' page with WP 6.1 and above. + if ( ! $needs_custom_page ) { + return; + } + add_theme_page( __( 'Template Parts', 'gutenberg' ), __( 'Template Parts', 'gutenberg' ), @@ -98,7 +116,7 @@ static function( $classes ) { 'styles' => get_block_editor_theme_styles(), 'defaultTemplateTypes' => $indexed_template_types, 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), - 'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(), + 'supportsLayout' => wp_theme_has_theme_json(), 'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ), '__unstableHomeTemplate' => gutenberg_resolve_home_template(), ); diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php new file mode 100644 index 0000000000000..c06b385b7cc2c --- /dev/null +++ b/lib/compat/wordpress-6.2/block-patterns.php @@ -0,0 +1,62 @@ + _x( 'Buttons', 'Block pattern category', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'columns', + array( + 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'footer', + array( + 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'gallery', + array( + 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'header', + array( + 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'text', + array( + 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'query', + array( + 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Display post summaries in lists, grids, and other layouts.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'featured', + array( + 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ), + ) + ); +} +add_action( 'init', 'gutenberg_register_core_block_patterns_and_categories' ); diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php new file mode 100644 index 0000000000000..970b1c4a1a330 --- /dev/null +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php @@ -0,0 +1,78 @@ +get_fields_for_response( $request ); + $keys = array( 'name', 'label', 'description' ); + $data = array(); + foreach ( $keys as $key ) { + if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) { + $data[ $key ] = $item[ $key ]; + } + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + return rest_ensure_response( $data ); + } + + /** + * Retrieves the block pattern category schema, conforming to JSON Schema. + * + * @since 6.0.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'block-pattern-category', + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'description' => __( 'The category name.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'label' => array( + 'description' => __( 'The category label, in human readable format.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'The category description, in human readable format.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } +} 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 new file mode 100644 index 0000000000000..e10710e0f4709 --- /dev/null +++ b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php @@ -0,0 +1,35 @@ + add_action( 'switch_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); + * > add_action( 'start_previewing_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); + */ +add_action( 'switch_theme', 'wp_theme_clean_theme_json_cached_data' ); +add_action( 'start_previewing_theme', 'wp_theme_clean_theme_json_cached_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 new file mode 100644 index 0000000000000..4dbd9d0ba8bde --- /dev/null +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -0,0 +1,47 @@ +register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' ); diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php index 06f01e03b6121..9d67cbd3ef1ad 100644 --- a/lib/compat/wordpress-6.2/script-loader.php +++ b/lib/compat/wordpress-6.2/script-loader.php @@ -28,3 +28,38 @@ function gutenberg_register_vendor_scripts_62( $scripts ) { $script->deps = array_merge( $script->deps, array( 'wp-inert-polyfill' ) ); } add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts_62' ); + +/** + * This function takes care of adding inline styles + * in the proper place, depending on the theme in use. + * + * This method was added to core in 5.9.1, but with a single param ($style). The second param ($priority) was + * added post 6.0, so the 6.1 release needs to have wp_enqueue_block_support_styles updated to include this param. + * + * For block themes, it's loaded in the head. + * For classic ones, it's loaded in the body + * because the wp_head action happens before + * the render_block. + * + * @link https://core.trac.wordpress.org/ticket/53494. + * + * @deprecated 6.2 Block supports styles are now stored for enqueuing via the style engine API. See: packages/style-engine/README.md. + * + * @param string $style String containing the CSS styles to be added. + * @param int $priority To set the priority for the add_action. + */ +function gutenberg_enqueue_block_support_styles( $style, $priority = 10 ) { + _deprecated_function( __FUNCTION__, '6.2' ); + + $action_hook_name = 'wp_footer'; + if ( wp_is_block_theme() ) { + $action_hook_name = 'wp_head'; + } + add_action( + $action_hook_name, + static function () use ( $style ) { + echo "\n"; + }, + $priority + ); +} diff --git a/lib/experimental/block-editor-settings-mobile.php b/lib/experimental/block-editor-settings-mobile.php index b0da17e929667..91e3694c199f8 100644 --- a/lib/experimental/block-editor-settings-mobile.php +++ b/lib/experimental/block-editor-settings-mobile.php @@ -22,7 +22,7 @@ function gutenberg_get_block_editor_settings_mobile( $settings ) { isset( $_GET['context'] ) && 'mobile' === $_GET['context'] ) { - if ( WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + if ( wp_theme_has_theme_json() ) { $settings['__experimentalStyles'] = gutenberg_get_global_styles(); } diff --git a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php index bab54c9410ac8..2f1ee93db30b5 100644 --- a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php @@ -15,7 +15,7 @@ * * @access private */ -class WP_Theme_JSON_Resolver_Gutenberg extends WP_Theme_JSON_Resolver_6_1 { +class WP_Theme_JSON_Resolver_Gutenberg extends WP_Theme_JSON_Resolver_6_2 { /** * Returns the theme's data. * @@ -73,7 +73,7 @@ public static function get_theme_data( $deprecated = array(), $settings = array( * and merge the static::$theme upon that. */ $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() ); - if ( ! static::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { if ( ! isset( $theme_support_data['settings']['color'] ) ) { $theme_support_data['settings']['color'] = array(); } diff --git a/lib/experimental/class-wp-webfonts.php b/lib/experimental/class-wp-webfonts.php index 02083d50cabc1..e40aca9e8671f 100644 --- a/lib/experimental/class-wp-webfonts.php +++ b/lib/experimental/class-wp-webfonts.php @@ -249,8 +249,8 @@ public function validate_webfont( $webfont ) { } $valid_props = array( - 'ascend-override', - 'descend-override', + 'ascent-override', + 'descent-override', 'font-display', 'font-family', 'font-stretch', diff --git a/lib/load.php b/lib/load.php index 65e8e3e2cb220..d457271d5eb37 100644 --- a/lib/load.php +++ b/lib/load.php @@ -49,6 +49,11 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/rest-api.php'; + // WordPress 6.2 compat. + require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php'; + require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php'; + require_once __DIR__ . '/compat/wordpress-6.2/block-patterns.php'; + // Experimental. if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { require_once __DIR__ . '/experimental/class-wp-rest-customizer-nonces.php'; @@ -97,6 +102,9 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.2 compat. require __DIR__ . '/compat/wordpress-6.2/script-loader.php'; +require __DIR__ . '/compat/wordpress-6.2/get-global-styles-and-settings.php'; +require __DIR__ . '/compat/wordpress-6.2/default-filters.php'; +require __DIR__ . '/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php'; // Experimental features. remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WP 6.0's stopgap handler for Webfonts API. diff --git a/package-lock.json b/package-lock.json index a456100f2e462..90aefe53f89c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,34 @@ { "name": "gutenberg", - "version": "14.4.0-rc.1", + "version": "14.5.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { "@actions/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz", - "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz", + "integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==", "dev": true, "requires": { - "@actions/http-client": "^1.0.11" + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + }, + "dependencies": { + "@actions/http-client": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", + "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "dev": true, + "requires": { + "tunnel": "^0.0.6" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "@actions/github": { @@ -1763,23 +1781,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@choojs/findup": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", - "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", - "dev": true, - "requires": { - "commander": "^2.15.1" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "@cnakazawa/watch": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", @@ -7970,19 +7971,19 @@ } }, "@playwright/test": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.1.tgz", - "integrity": "sha512-IJ4X0yOakXtwkhbnNzKkaIgXe6df7u3H3FnuhI9Jqh+CdO0e/lYQlDLYiyI9cnXK8E7UAppAWP+VqAv6VX7HQg==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.27.1.tgz", + "integrity": "sha512-mrL2q0an/7tVqniQQF6RBL2saskjljXzqNcCOVMUjRIgE6Y38nCNaP+Dc2FBW06bcpD3tqIws/HT9qiMHbNU0A==", "dev": true, "requires": { "@types/node": "*", - "playwright-core": "1.25.1" + "playwright-core": "1.27.1" }, "dependencies": { "playwright-core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.1.tgz", - "integrity": "sha512-lSvPCmA2n7LawD2Hw7gSCLScZ+vYRkhU8xH0AapMyzwN+ojoDqhkH/KIEUxwNu2PjPoE/fcE0wLAksdOhJ2O5g==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", + "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", "dev": true } } @@ -16966,6 +16967,243 @@ } } }, + "@typescript-eslint/utils": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.40.0.tgz", + "integrity": "sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.40.0", + "@typescript-eslint/types": "5.40.0", + "@typescript-eslint/typescript-estree": "5.40.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@typescript-eslint/scope-manager": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz", + "integrity": "sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.40.0", + "@typescript-eslint/visitor-keys": "5.40.0" + } + }, + "@typescript-eslint/types": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.40.0.tgz", + "integrity": "sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz", + "integrity": "sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.40.0", + "@typescript-eslint/visitor-keys": "5.40.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz", + "integrity": "sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.40.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "@typescript-eslint/visitor-keys": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.0.tgz", @@ -17356,6 +17594,7 @@ "@wordpress/url": "file:packages/url", "@wordpress/warning": "file:packages/warning", "@wordpress/wordcount": "file:packages/wordcount", + "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", "diff": "^4.0.2", @@ -17402,6 +17641,7 @@ "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", + "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", "lodash": "^4.17.21", "memize": "^1.1.0", @@ -17886,11 +18126,20 @@ "@wordpress/url": "file:packages/url", "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.3.1", + "date-fns": "^2.28.0", + "escape-html": "^1.0.3", "lodash": "^4.17.21", "memize": "^1.1.0", "react-autosize-textarea": "^7.1.0", "rememo": "^4.0.0", "remove-accents": "^0.4.2" + }, + "dependencies": { + "date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" + } } }, "@wordpress/element": { @@ -18308,7 +18557,7 @@ "version": "file:packages/project-management-automation", "dev": true, "requires": { - "@actions/core": "1.8.0", + "@actions/core": "1.9.1", "@actions/github": "^5.0.0", "@babel/runtime": "^7.16.0", "@octokit/request-error": "^2.1.0", @@ -19350,7 +19599,7 @@ "app-root-dir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", + "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", "dev": true }, "app-root-path": { @@ -27632,7 +27881,7 @@ "babel-plugin-add-react-displayname": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", - "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", + "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=", "dev": true }, "babel-plugin-apply-mdx-type-prop": { @@ -28055,7 +28304,7 @@ "batch-processor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz", - "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==", + "integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=", "dev": true }, "bcrypt-pbkdf": { @@ -31306,7 +31555,7 @@ "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", "dev": true }, "cssesc": { @@ -34114,6 +34363,15 @@ "globals": "^13.8.0" } }, + "eslint-plugin-testing-library": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.7.2.tgz", + "integrity": "sha512-0ZmHeR/DUUgEzW8rwUBRWxuqntipDtpvxK0hymdHnLlABryJkzd+CAHr+XnISaVsTisZ5MLHp6nQF+8COHLLTA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^5.13.0" + } + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -36858,7 +37116,7 @@ "has-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", - "integrity": "sha512-D+8A457fBShSEI3tFCj65PAbT++5sKiFtdCdOam0gnfBgw9D277OERk+HM9qYJXmdVLZ/znez10SqHN0BBQ50g==", + "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", "dev": true, "requires": { "is-glob": "^3.0.0" @@ -36867,7 +37125,7 @@ "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { "is-extglob": "^2.1.0" @@ -38754,7 +39012,7 @@ "is-window": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", - "integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg==", + "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=", "dev": true }, "is-windows": { @@ -42131,7 +42389,7 @@ "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", "dev": true }, "js-tokens": { @@ -43539,7 +43797,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.ismatch": { "version": "4.4.0", @@ -43819,7 +44077,7 @@ "lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", "dev": true }, "macos-release": { @@ -47064,7 +47322,7 @@ "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, "number-is-nan": { @@ -48542,7 +48800,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-event": { @@ -49427,14 +49685,22 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", - "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "dev": true, "requires": { - "nanoid": "^3.1.30", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + } } }, "postcss-calc": { @@ -50108,7 +50374,7 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, "prismjs": { @@ -52414,7 +52680,7 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, "remark": { @@ -53264,46 +53530,15 @@ "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==" }, "rtlcss": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.6.2.tgz", - "integrity": "sha512-06LFAr+GAPo+BvaynsXRfoYTJvSaWRyOhURCQ7aeI1MKph9meM222F+Zkt3bDamyHHJuGi3VPtiRkpyswmQbGA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.0.0.tgz", + "integrity": "sha512-j6oypPP+mgFwDXL1JkLCtm6U/DQntMUqlv5SOhpgHhdIE+PmBcjrtAHIpXfbIup47kD5Sgja9JDsDF1NNOsBwQ==", "dev": true, "requires": { - "@choojs/findup": "^0.2.1", - "chalk": "^2.4.2", - "mkdirp": "^0.5.1", - "postcss": "^6.0.23", - "strip-json-comments": "^2.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.6", + "strip-json-comments": "^3.1.1" } }, "run-async": { @@ -55959,9 +56194,9 @@ } }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "strip-outer": { diff --git a/package.json b/package.json old mode 100755 new mode 100644 index c7c955ffc570b..88cd1197fe65e --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.4.0-rc.1", + "version": "14.5.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -88,7 +88,7 @@ "wicg-inert": "3.1.2" }, "devDependencies": { - "@actions/core": "1.8.0", + "@actions/core": "1.9.1", "@actions/github": "5.0.0", "@babel/core": "7.16.0", "@babel/plugin-syntax-jsx": "7.16.0", @@ -100,7 +100,7 @@ "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.6.0", - "@playwright/test": "1.25.1", + "@playwright/test": "1.27.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.2", "@storybook/addon-a11y": "6.5.7", "@storybook/addon-actions": "6.5.7", @@ -182,12 +182,14 @@ "cssnano": "5.0.7", "deep-freeze": "0.0.1", "equivalent-key-map": "0.2.2", + "escape-html": "1.0.3", "eslint-import-resolver-node": "0.3.4", "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.25.2", "eslint-plugin-jest-dom": "4.0.2", "eslint-plugin-playwright": "0.8.0", "eslint-plugin-ssr-friendly": "1.0.6", + "eslint-plugin-testing-library": "5.7.2", "execa": "4.0.2", "fast-glob": "3.2.7", "filenamify": "4.2.0", @@ -211,7 +213,7 @@ "node-watch": "0.7.0", "npm-run-all": "4.1.5", "patch-package": "6.2.2", - "postcss": "8.4.5", + "postcss": "8.4.16", "postcss-loader": "6.2.1", "prettier": "npm:wp-prettier@2.6.2", "progress": "2.0.3", @@ -224,7 +226,7 @@ "redux": "4.1.2", "resize-observer-polyfill": "1.5.1", "rimraf": "3.0.2", - "rtlcss": "2.6.2", + "rtlcss": "4.0.0", "sass": "1.35.2", "sass-loader": "12.1.0", "semver": "7.3.5", diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 1874e6f7852bc..25ebaac9760a8 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fix + +- `InserterListItem`: Fix dragging and dropping in Firefox. ([#44631](https://github.com/WordPress/gutenberg/pull/44631)) + ## 10.3.0 (2022-10-19) ### Bug Fix diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index abe8775b3028d..b5d6212930a86 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -59,6 +59,7 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", "diff": "^4.0.2", diff --git a/packages/block-editor/src/components/alignment-control/ui.js b/packages/block-editor/src/components/alignment-control/ui.js index ed6c40f54b70b..f50e69044f9e5 100644 --- a/packages/block-editor/src/components/alignment-control/ui.js +++ b/packages/block-editor/src/components/alignment-control/ui.js @@ -30,7 +30,7 @@ const DEFAULT_ALIGNMENT_CONTROLS = [ const POPOVER_PROPS = { position: 'bottom right', - isAlternate: true, + variant: 'toolbar', }; function AlignmentUI( { diff --git a/packages/block-editor/src/components/block-alignment-control/constants.js b/packages/block-editor/src/components/block-alignment-control/constants.js index f32a815b02f67..2fa133740a4cf 100644 --- a/packages/block-editor/src/components/block-alignment-control/constants.js +++ b/packages/block-editor/src/components/block-alignment-control/constants.js @@ -41,5 +41,5 @@ export const BLOCK_ALIGNMENTS_CONTROLS = { export const DEFAULT_CONTROL = 'none'; export const POPOVER_PROPS = { - isAlternate: true, + variant: 'toolbar', }; diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/index.js b/packages/block-editor/src/components/block-alignment-matrix-control/index.js index 6e9ef0def5a5a..6ea7b2a49c8a9 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-control/index.js +++ b/packages/block-editor/src/components/block-alignment-matrix-control/index.js @@ -24,7 +24,7 @@ function BlockAlignmentMatrixControl( props ) { return ( { const openOnArrowDown = ( event ) => { if ( ! isOpen && event.keyCode === DOWN ) { diff --git a/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap index 85dd26bc0a7f3..01f2d73d3e5f9 100644 --- a/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap @@ -1,16 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`BlockBreadcrumb should render correctly 1`] = ` -
    -
  • +
      - Document - -
    +
  • + Document +
  • +
+ `; diff --git a/packages/block-editor/src/components/block-breadcrumb/test/index.js b/packages/block-editor/src/components/block-breadcrumb/test/index.js index ee8d2bc57af03..dcdf242831c71 100644 --- a/packages/block-editor/src/components/block-breadcrumb/test/index.js +++ b/packages/block-editor/src/components/block-breadcrumb/test/index.js @@ -12,7 +12,7 @@ describe( 'BlockBreadcrumb', () => { it( 'should render correctly', () => { const { container } = render( ); - expect( container.firstChild ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); } ); describe( 'Root label text', () => { diff --git a/packages/block-editor/src/components/block-compare/index.js b/packages/block-editor/src/components/block-compare/index.js index b04cd3238dc9b..46454f82a070d 100644 --- a/packages/block-editor/src/components/block-compare/index.js +++ b/packages/block-editor/src/components/block-compare/index.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { castArray } from 'lodash'; // diff doesn't tree-shake correctly, so we import from the individual // module here, to avoid including too much of the library import { diffChars } from 'diff/lib/diff/character'; @@ -44,7 +43,9 @@ function BlockCompare( { function getConvertedContent( convertedBlock ) { // The convertor may return an array of items or a single item. - const newBlocks = castArray( convertedBlock ); + const newBlocks = Array.isArray( convertedBlock ) + ? convertedBlock + : [ convertedBlock ]; // Get converted block details. const newContent = newBlocks.map( ( item ) => diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 9b2875b3018a3..747e25b820847 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -20,11 +20,12 @@ import { BlockEditContextProvider, useBlockEditContext } from './context'; export { useBlockEditContext }; export default function BlockEdit( props ) { - const { name, isSelected, clientId } = props; + const { name, isSelected, clientId, __unstableLayoutClassNames } = props; const context = { name, isSelected, clientId, + __unstableLayoutClassNames, }; return ( ); diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 918906e62f8cc..66569fc6847a2 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -174,7 +174,7 @@ // With `position: static`, Safari marks a full-width selection rectangle, including margins. // With `position: relative`, Safari marks an inline selection rectangle, similar to that of // Blink based browsers, but it also does "crop" the marker, which can result in a small line - // from the preceeding paragraph showing, which is effectively the above selection bleeding in. + // from the preceding paragraph showing, which is effectively the above selection bleeding in. // We choose relative, as that matches the multi-selection, which is limited to the block footprint. position: relative; diff --git a/packages/block-editor/src/components/block-mover/stories/index.js b/packages/block-editor/src/components/block-mover/stories/index.js index a6b5fad872f0b..aac98ecc84d6e 100644 --- a/packages/block-editor/src/components/block-mover/stories/index.js +++ b/packages/block-editor/src/components/block-mover/stories/index.js @@ -70,7 +70,7 @@ function BlockMoverStory() {

- But it can also accomodate horizontal blocks. + But it can also accommodate horizontal blocks.

{ __unstableCoverTarget &&
{ children }
} { ! __unstableCoverTarget && children } diff --git a/packages/block-editor/src/components/block-popover/style.scss b/packages/block-editor/src/components/block-popover/style.scss index 1744506179e67..2ce92991c9054 100644 --- a/packages/block-editor/src/components/block-popover/style.scss +++ b/packages/block-editor/src/components/block-popover/style.scss @@ -13,10 +13,6 @@ margin: 0 !important; min-width: auto; width: max-content; - background: none; - border: none; - outline: none; - box-shadow: none; overflow-y: visible; } diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 02d68203d7eb3..b01411591f098 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { castArray } from 'lodash'; import classnames from 'classnames'; /** @@ -36,7 +35,10 @@ export function BlockPreview( { () => ( { ...originalSettings, __unstableIsPreviewMode: true } ), [ originalSettings ] ); - const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); + const renderedBlocks = useMemo( + () => ( Array.isArray( blocks ) ? blocks : [ blocks ] ), + [ blocks ] + ); if ( ! blocks || blocks.length === 0 ) { return null; } @@ -99,7 +101,10 @@ export function useBlockPreview( { ); const disabledRef = useDisabled(); const ref = useMergeRefs( [ props.ref, disabledRef ] ); - const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); + const renderedBlocks = useMemo( + () => ( Array.isArray( blocks ) ? blocks : [ blocks ] ), + [ blocks ] + ); const children = ( diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index df2111da3b33e..87fc6bc5e0585 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { castArray } from 'lodash'; - /** * WordPress dependencies */ @@ -41,7 +36,7 @@ const noop = () => {}; const POPOVER_PROPS = { className: 'block-editor-block-settings-menu__popover', position: 'bottom right', - isAlternate: true, + variant: 'toolbar', }; function CopyMenuItem( { blocks, onCopy } ) { @@ -58,7 +53,9 @@ export function BlockSettingsDropdown( { __unstableDisplayLocation, ...props } ) { - const blockClientIds = castArray( clientIds ); + const blockClientIds = Array.isArray( clientIds ) + ? clientIds + : [ clientIds ]; const count = blockClientIds.length; const firstBlockClientId = blockClientIds[ 0 ]; const { diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 8213e60ec2f56..462d94f40089c 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { castArray } from 'lodash'; - /** * WordPress dependencies */ @@ -52,7 +47,7 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { const { getBlockStyles, getBlockType } = select( blocksStore ); const { canRemoveBlocks } = select( blockEditorStore ); const rootClientId = getBlockRootClientId( - castArray( clientIds )[ 0 ] + Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds ); const [ { name: firstBlockName } ] = blocks; const _isSingleBlockSelected = blocks.length === 1; @@ -163,7 +158,7 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { label={ blockSwitcherLabel } popoverProps={ { position: 'bottom right', - isAlternate: true, + variant: 'toolbar', className: 'block-editor-block-switcher__popover', } } icon={ diff --git a/packages/block-editor/src/components/block-switcher/preview-block-popover.js b/packages/block-editor/src/components/block-switcher/preview-block-popover.js index 53adb87f337ad..78e29f4ad09fb 100644 --- a/packages/block-editor/src/components/block-switcher/preview-block-popover.js +++ b/packages/block-editor/src/components/block-switcher/preview-block-popover.js @@ -15,7 +15,7 @@ export default function PreviewBlockPopover( { blocks } ) {
diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index 3b950ef87a754..b7cd30462e359 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -25,7 +25,7 @@ function InbetweenInsertionPointPopover( { __unstablePopoverSlot, __unstableContentRef, } ) { - const { selectBlock } = useDispatch( blockEditorStore ); + const { selectBlock, hideInsertionPoint } = useDispatch( blockEditorStore ); const openRef = useContext( InsertionPointOpenRef ); const ref = useRef(); const { @@ -89,6 +89,14 @@ function InbetweenInsertionPointPopover( { } } + function maybeHideInserterPoint( event ) { + // Only hide the inserter if it's triggered on the wrapper, + // and the inserter is not open. + if ( event.target === ref.current && ! openRef.current ) { + hideInsertionPoint(); + } + } + function onFocus( event ) { // Only handle click on the wrapper specifically, and not an event // bubbled from the inserter itself. @@ -200,6 +208,7 @@ function InbetweenInsertionPointPopover( { className={ classnames( className, { 'is-with-inserter': isInserterShown, } ) } + onHoverEnd={ maybeHideInserterPoint } > ); } diff --git a/packages/block-editor/src/components/border-radius-control/index.js b/packages/block-editor/src/components/border-radius-control/index.js index 9c2e96ba99b93..8754d39ac570f 100644 --- a/packages/block-editor/src/components/border-radius-control/index.js +++ b/packages/block-editor/src/components/border-radius-control/index.js @@ -113,6 +113,7 @@ export default function BorderRadiusControl( { onChange, values } ) { withInputField={ false } onChange={ handleSliderChange } step={ step } + __nextHasNoMarginBottom /> ) : ( diff --git a/packages/block-editor/src/components/border-radius-control/input-controls.js b/packages/block-editor/src/components/border-radius-control/input-controls.js index aa32c68c1a472..b33b79a482435 100644 --- a/packages/block-editor/src/components/border-radius-control/input-controls.js +++ b/packages/block-editor/src/components/border-radius-control/input-controls.js @@ -80,6 +80,7 @@ export default function BoxInputControls( { onUnitChange={ createHandleOnUnitChange( corner ) } + size={ '__unstable-large' } />
diff --git a/packages/block-editor/src/components/border-radius-control/style.scss b/packages/block-editor/src/components/border-radius-control/style.scss index 7e4f220709924..c94dfbbd0a8fd 100644 --- a/packages/block-editor/src/components/border-radius-control/style.scss +++ b/packages/block-editor/src/components/border-radius-control/style.scss @@ -10,24 +10,21 @@ justify-content: space-between; align-items: flex-start; - > .components-unit-control-wrapper { - width: 110px; + .components-border-radius-control__unit-control { + width: calc((100% - #{$grid-unit-20}) / 2); margin-bottom: 0; - margin-right: #{ $grid-unit-15 }; + margin-right: $grid-unit-20; flex-shrink: 0; } - .components-range-control { + .components-border-radius-control__range-control { flex: 1; - margin-bottom: 0; - - .components-base-control__field { - margin-bottom: 0; - height: 30px; - } + margin-right: $grid-unit-15; - .components-range-control__wrapper { - margin-right: 10px; + > div { + height: 40px; + display: flex; + align-items: center; } } @@ -37,22 +34,16 @@ } .components-border-radius-control__input-controls-wrapper { - display: flex; - width: 70%; - flex-wrap: wrap; - - .components-border-radius-control__tooltip-wrapper { - width: calc(50% - #{ $grid-unit-10 }); - margin-bottom: $grid-unit-10; - margin-right: $grid-unit-10; - } + display: grid; + gap: $grid-unit-20; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-right: $grid-unit-15; } - .component-border-radius-control__linked-button.has-icon { + .component-border-radius-control__linked-button { display: flex; justify-content: center; - margin-left: 2px; - margin-top: 3px; + margin-top: $grid-unit-10; svg { margin-right: 0; diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap index 6ea0541d50696..ddc08d9985fbb 100644 --- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap +++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap @@ -117,114 +117,116 @@ exports[`ColorPaletteControl matches the snapshot 1`] = ` min-width: 0; } -
+
-
-
- -
- - Test Color - -
-
+ +
+ + Test Color + +
+
- -
-
+ + red + + + f00 + + +
-
-
-
- + +
-
- + +
`; diff --git a/packages/block-editor/src/components/color-palette/test/control.js b/packages/block-editor/src/components/color-palette/test/control.js index e3e90ea0bac83..3a5dcc657a94c 100644 --- a/packages/block-editor/src/components/color-palette/test/control.js +++ b/packages/block-editor/src/components/color-palette/test/control.js @@ -22,6 +22,6 @@ describe( 'ColorPaletteControl', () => { /> ); - expect( container.firstChild ).toMatchSnapshot(); + expect( container ).toMatchSnapshot(); } ); } ); diff --git a/packages/block-editor/src/components/duotone-control/index.js b/packages/block-editor/src/components/duotone-control/index.js index aeb67c8a9b878..d27e6863fe0df 100644 --- a/packages/block-editor/src/components/duotone-control/index.js +++ b/packages/block-editor/src/components/duotone-control/index.js @@ -36,7 +36,7 @@ function DuotoneControl( { popoverProps={ { className: 'block-editor-duotone-control__popover', headerTitle: __( 'Duotone' ), - isAlternate: true, + variant: 'toolbar', } } renderToggle={ ( { isOpen, onToggle } ) => { const openOnArrowDown = ( event ) => { diff --git a/packages/block-editor/src/components/font-sizes/with-font-sizes.js b/packages/block-editor/src/components/font-sizes/with-font-sizes.js index f8b37a1498c21..5d9956c8b91f1 100644 --- a/packages/block-editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/block-editor/src/components/font-sizes/with-font-sizes.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { find, pickBy, reduce, some } from 'lodash'; +import { find, pickBy, reduce } from 'lodash'; /** * WordPress dependencies @@ -152,8 +152,7 @@ export default ( ...fontSizeNames ) => { }; if ( - ! some( - fontSizeAttributeNames, + ! Object.values( fontSizeAttributeNames ).some( didAttributesChange ) ) { diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 29fe41f891636..25e36037ebac5 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -282,7 +282,7 @@ function Iframe( head = ( <> - + { styles.map( ( { tagName, href, id, rel, media, textContent } ) => { const TagName = tagName.toLowerCase(); diff --git a/packages/block-editor/src/components/image-editor/constants.js b/packages/block-editor/src/components/image-editor/constants.js index 0692a76895ca1..b2419aa07b7ce 100644 --- a/packages/block-editor/src/components/image-editor/constants.js +++ b/packages/block-editor/src/components/image-editor/constants.js @@ -2,5 +2,5 @@ export const MIN_ZOOM = 100; export const MAX_ZOOM = 300; export const POPOVER_PROPS = { position: 'bottom right', - isAlternate: true, + variant: 'toolbar', }; diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 4105d7291957e..8afc6482eb33f 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -150,7 +150,9 @@ const ForwardedInnerBlocks = forwardRef( ( props, ref ) => { * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md */ export function useInnerBlocksProps( props = {}, options = {} ) { - const { clientId } = useBlockEditContext(); + const { __unstableDisableLayoutClassNames } = options; + const { clientId, __unstableLayoutClassNames: layoutClassNames = '' } = + useBlockEditContext(); const isSmallScreen = useViewportMatch( 'medium', '<' ); const { __experimentalCaptureToolbars, hasOverlay } = useSelect( ( select ) => { @@ -200,12 +202,14 @@ export function useInnerBlocksProps( props = {}, options = {} ) { innerBlocksProps.value && innerBlocksProps.onChange ? ControlledInnerBlocks : UncontrolledInnerBlocks; + return { ...props, ref, className: classnames( props.className, 'block-editor-block-list__layout', + __unstableDisableLayoutClassNames ? '' : layoutClassNames, { 'has-overlay': hasOverlay, } diff --git a/packages/block-editor/src/components/inner-blocks/test/index.js b/packages/block-editor/src/components/inner-blocks/test/index.js index 04018b65eda35..c9d9279db2745 100644 --- a/packages/block-editor/src/components/inner-blocks/test/index.js +++ b/packages/block-editor/src/components/inner-blocks/test/index.js @@ -1,3 +1,5 @@ +/* eslint-disable testing-library/render-result-naming-convention */ + /** * WordPress dependencies */ @@ -102,3 +104,5 @@ describe( 'InnerBlocks', () => { expect( serialize( block ) ).toMatchSnapshot(); } ); } ); + +/* eslint-enable testing-library/render-result-naming-convention */ diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index 981d470a81d6a..b3078adb519f1 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -49,6 +49,8 @@ border-radius: $radius-block-ui; opacity: 0.04; background: var(--wp-admin-theme-color); + // This fixes drag-and-drop in Firefox. + pointer-events: none; } } diff --git a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js index fddc14f8dff1f..d2bbaa909d0a2 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js +++ b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { castArray } from 'lodash'; - /** * WordPress dependencies */ @@ -118,14 +113,11 @@ function useInsertionPoint( { meta ); } + const blockLength = Array.isArray( blocks ) ? blocks.length : 1; const message = sprintf( // translators: %d: the name of the block that has been added - _n( - '%d block added.', - '%d blocks added.', - castArray( blocks ).length - ), - castArray( blocks ).length + _n( '%d block added.', '%d blocks added.', blockLength ), + blockLength ); speak( message ); diff --git a/packages/block-editor/src/components/inserter/search-items.js b/packages/block-editor/src/components/inserter/search-items.js index f8edd14fc5ff4..5451f19937b01 100644 --- a/packages/block-editor/src/components/inserter/search-items.js +++ b/packages/block-editor/src/components/inserter/search-items.js @@ -2,7 +2,8 @@ * External dependencies */ import removeAccents from 'remove-accents'; -import { find, words } from 'lodash'; +import { find } from 'lodash'; +import { noCase } from 'change-case'; // Default search helpers. const defaultGetName = ( item ) => item.name || ''; @@ -12,6 +13,25 @@ const defaultGetKeywords = ( item ) => item.keywords || []; const defaultGetCategory = ( item ) => item.category; const defaultGetCollection = () => null; +/** + * Extracts words from an input string. + * + * @param {string} input The input string. + * + * @return {Array} Words, extracted from the input string. + */ +function extractWords( input = '' ) { + return noCase( input, { + splitRegexp: [ + /([\p{Ll}\p{Lo}\p{N}])([\p{Lu}\p{Lt}])/gu, // One lowercase or digit, followed by one uppercase. + /([\p{Lu}\p{Lt}])([\p{Lu}\p{Lt}][\p{Ll}\p{Lo}])/gu, // One uppercase followed by one uppercase and one lowercase. + ], + stripRegexp: /(\p{C}|\p{P}|\p{S})+/giu, // Anything that's not a punctuation, symbol or control/format character. + } ) + .split( ' ' ) + .filter( Boolean ); +} + /** * Sanitizes the search input string. * @@ -43,7 +63,7 @@ function normalizeSearchInput( input = '' ) { * @return {string[]} The normalized list of search terms. */ export const getNormalizedSearchTerms = ( input = '' ) => { - return words( normalizeSearchInput( input ) ); + return extractWords( normalizeSearchInput( input ) ); }; const removeMatchingTerms = ( unmatchedTerms, unprocessedTerms ) => { @@ -150,7 +170,7 @@ export function getItemSearchRank( item, searchTerm, config = {} ) { category, collection, ].join( ' ' ); - const normalizedSearchTerms = words( normalizedSearchInput ); + const normalizedSearchTerms = extractWords( normalizedSearchInput ); const unmatchedTerms = removeMatchingTerms( normalizedSearchTerms, terms diff --git a/packages/block-editor/src/components/inserter/test/search-items.js b/packages/block-editor/src/components/inserter/test/search-items.js index a33da9d580230..057432113b731 100644 --- a/packages/block-editor/src/components/inserter/test/search-items.js +++ b/packages/block-editor/src/components/inserter/test/search-items.js @@ -45,6 +45,12 @@ describe( 'getNormalizedSearchTerms', () => { it( 'should support non-latin letters', () => { expect( getNormalizedSearchTerms( 'მედია' ) ).toEqual( [ 'მედია' ] ); + expect( + getNormalizedSearchTerms( '师父领进门,修行在个人。' ) + ).toEqual( [ '师父领进门', '修行在个人' ] ); + expect( + getNormalizedSearchTerms( 'Бързата работа – срам за майстора.' ) + ).toEqual( [ 'бързата', 'работа', 'срам', 'за', 'майстора' ] ); } ); } ); diff --git a/packages/block-editor/src/components/letter-spacing-control/README.md b/packages/block-editor/src/components/letter-spacing-control/README.md new file mode 100644 index 0000000000000..fb4dd4fd23dac --- /dev/null +++ b/packages/block-editor/src/components/letter-spacing-control/README.md @@ -0,0 +1,55 @@ +# Letter spacing control + +The `LetterSpacingControl` component renders a [`UnitControl`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/unit-control/README.md) that lets the user enter a numeric value and select a unit, for example px or rem. + +This component is used for blocks that display text, commonly inside a +[`ToolsPanelItem`](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/tools-panel/tools-panel-item/README.md). + +## Table of contents + +1. [Development guidelines](#development-guidelines) +2. [Related components](#related-components) + +## Development guidelines + +### Usage + +Renders a letter spacing control. + +```jsx +import { LetterSpacingControl } from '@wordpress/block-editor'; + +const MyLetterSpacingControl = () => ( + +); +``` + +### Props + +### `value` + +- **Type:** `String` +- **Default:** `undefined` + +The current value of the letter spacing setting. + +### `onChange` + +- **Type:** `Function` + +A callback function invoked when the value is changed. + +### `_unstableInputWidth` + +- **Type:** `string|number|undefined` +- **Default:** `undefined` + +Input width to pass through to inner UnitControl. Should be a valid CSS value. + +## Related components + +Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. diff --git a/packages/block-editor/src/components/line-height-control/index.js b/packages/block-editor/src/components/line-height-control/index.js index 127668002c7ac..670d6fac37e8d 100644 --- a/packages/block-editor/src/components/line-height-control/index.js +++ b/packages/block-editor/src/components/line-height-control/index.js @@ -99,6 +99,7 @@ const LineHeightControl = ( { step={ STEP } value={ value } min={ 0 } + spinControls="custom" /> ); diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index ba6a4bbba5268..3d3dd7b0aaa21 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -192,7 +192,7 @@ A `suggestion` should have the following shape: )} /> ``` -### renderControlBottom +### renderControlBottom - Type: `Function` - Required: No @@ -338,7 +338,7 @@ See the [createSuggestion](#createSuggestion) section of this file to learn more { return ( - +
    { suggestions.map( () => (
  • { suggestion.title }
  • ) ) }
@@ -352,7 +352,7 @@ See the [createSuggestion](#createSuggestion) section of this file to learn more { return ( - + ); diff --git a/packages/block-editor/src/components/list-view/drop-indicator.js b/packages/block-editor/src/components/list-view/drop-indicator.js index 1500e2f887fad..1e8d51a73919a 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator.js +++ b/packages/block-editor/src/components/list-view/drop-indicator.js @@ -115,6 +115,7 @@ export default function ListViewDropIndicator( { anchor={ popoverAnchor } focusOnMount={ false } className="block-editor-list-view-drop-indicator" + variant="unstyled" >
.components-popover__content { - margin-left: 0; - border: none; - box-shadow: none; - outline: none; -} - .block-editor-list-view-placeholder { padding: 0; margin: 0; diff --git a/packages/block-editor/src/components/media-replace-flow/index.js b/packages/block-editor/src/components/media-replace-flow/index.js index 21478b517c597..a2fb5f36036aa 100644 --- a/packages/block-editor/src/components/media-replace-flow/index.js +++ b/packages/block-editor/src/components/media-replace-flow/index.js @@ -131,7 +131,7 @@ const MediaReplaceFlow = ( { const gallery = multiple && onlyAllowsImages(); const POPOVER_PROPS = { - isAlternate: true, + variant: 'toolbar', }; return ( 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 5e686386de60c..8d6bc6ca43378 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,8 @@ /** * External dependencies */ -import { render, fireEvent } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * WordPress dependencies @@ -31,80 +32,97 @@ function TestWrapper() { ); } -function setUpMediaReplaceFlow() { - const { container } = render( ); - return container; -} - describe( 'General media replace flow', () => { it( 'renders successfully', () => { - const container = setUpMediaReplaceFlow(); - - const mediaReplaceButton = container.querySelector( - 'button[aria-expanded="false"]' - ); - - expect( mediaReplaceButton ).not.toBeNull(); + render( ); + + expect( + screen.getByRole( 'button', { + expanded: false, + name: 'Replace', + } ) + ).toBeVisible(); } ); - it( 'renders replace menu', () => { - const container = setUpMediaReplaceFlow(); + it( 'renders replace menu', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); - const mediaReplaceButton = container.querySelector( - 'button[aria-expanded="false"]' - ); - mediaReplaceButton.click(); + render( ); - const uploadMenu = container.querySelector( - '.block-editor-media-replace-flow__media-upload-menu' + await user.click( + screen.getByRole( 'button', { + expanded: false, + name: 'Replace', + } ) ); - expect( uploadMenu ).not.toBeNull(); + const uploadMenu = screen.getByRole( 'menu' ); + + expect( uploadMenu ).toBeInTheDocument(); + expect( uploadMenu ).not.toBeVisible(); } ); - it( 'displays media URL', () => { - const container = setUpMediaReplaceFlow(); + it( 'displays media URL', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); - const mediaReplaceButton = container.querySelector( - 'button[aria-expanded="false"]' - ); - mediaReplaceButton.click(); + render( ); - const mediaURL = container.querySelector( '.components-external-link' ); + await user.click( + screen.getByRole( 'button', { + expanded: false, + name: 'Replace', + } ) + ); - expect( mediaURL.href ).toEqual( 'https://example.media/' ); + expect( + screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ) + ).toHaveAttribute( 'href', 'https://example.media' ); } ); - it( 'edits media URL', () => { - const container = setUpMediaReplaceFlow(); + it( 'edits media URL', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); - const mediaReplaceButton = container.querySelector( - 'button[aria-expanded="false"]' - ); - mediaReplaceButton.click(); + render( ); - const editMediaURL = container.querySelector( - '.block-editor-link-control__search-item-action' + await user.click( + screen.getByRole( 'button', { + expanded: false, + name: 'Replace', + } ) ); - editMediaURL.click(); - - const mediaURLInput = container.querySelector( - '.block-editor-url-input__input' + await user.click( + screen.getByRole( 'button', { + name: 'Edit', + } ) ); - fireEvent.change( mediaURLInput, { - target: { value: 'https://new.example.media' }, + const mediaURLInput = screen.getByRole( 'combobox', { + name: 'URL', + expanded: false, } ); - const saveMediaURLButton = container.querySelector( - '.block-editor-link-control__search-submit' - ); - - saveMediaURLButton.click(); + await user.clear( mediaURLInput ); + await user.type( mediaURLInput, 'https://new.example.media' ); - const mediaURL = container.querySelector( '.components-external-link' ); + await user.click( + screen.getByRole( 'button', { + name: 'Submit', + } ) + ); - expect( mediaURL.href ).toEqual( 'https://new.example.media/' ); + expect( + screen.getByRole( 'link', { + name: 'new.example.media (opens in a new tab)', + } ) + ).toHaveAttribute( 'href', 'https://new.example.media' ); } ); } ); diff --git a/packages/block-editor/src/components/rich-text/format-toolbar-container.js b/packages/block-editor/src/components/rich-text/format-toolbar-container.js index 18027af27a05f..0f89dd4b248f7 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar-container.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar-container.js @@ -42,7 +42,7 @@ function InlineSelectionToolbar( { function InlineToolbar( { popoverAnchor } ) { return ( { diff --git a/packages/block-editor/src/components/rich-text/use-paste-handler.js b/packages/block-editor/src/components/rich-text/use-paste-handler.js index 6e5383aabc167..5e4f4260f5001 100644 --- a/packages/block-editor/src/components/rich-text/use-paste-handler.js +++ b/packages/block-editor/src/components/rich-text/use-paste-handler.js @@ -255,7 +255,7 @@ export function usePasteHandler( props ) { /** * Normalizes a given string of HTML to remove the Windows-specific "Fragment" - * comments and any preceeding and trailing content. + * comments and any preceding and trailing content. * * @param {string} html the html to be normalized * @return {string} the normalized html diff --git a/packages/block-editor/src/components/spacing-sizes-control/all-input-control.js b/packages/block-editor/src/components/spacing-sizes-control/all-input-control.js index 8f9509349c2a5..f7f9686daee33 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/all-input-control.js +++ b/packages/block-editor/src/components/spacing-sizes-control/all-input-control.js @@ -16,6 +16,8 @@ export default function AllInputControl( { spacingSizes, type, minimumCustomValue, + onMouseOver, + onMouseOut, } ) { const allValue = getAllRawValue( values ); const hasValues = isValuesDefined( values ); @@ -35,6 +37,8 @@ export default function AllInputControl( { isMixed={ isMixed } type={ type } minimumCustomValue={ minimumCustomValue } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ); } diff --git a/packages/block-editor/src/components/spacing-sizes-control/axial-input-controls.js b/packages/block-editor/src/components/spacing-sizes-control/axial-input-controls.js index 563b595018696..3551e20ede759 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/axial-input-controls.js +++ b/packages/block-editor/src/components/spacing-sizes-control/axial-input-controls.js @@ -13,6 +13,8 @@ export default function AxialInputControls( { spacingSizes, type, minimumCustomValue, + onMouseOver, + onMouseOut, } ) { const createHandleOnChange = ( side ) => ( next ) => { if ( ! onChange ) { @@ -54,6 +56,8 @@ export default function AxialInputControls( { spacingSizes={ spacingSizes } type={ type } minimumCustomValue={ minimumCustomValue } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ); } ) } diff --git a/packages/block-editor/src/components/spacing-sizes-control/index.js b/packages/block-editor/src/components/spacing-sizes-control/index.js index 7b016e7a08ee7..fb4ce2176b759 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/index.js +++ b/packages/block-editor/src/components/spacing-sizes-control/index.js @@ -29,6 +29,8 @@ export default function SpacingSizesControl( { splitOnAxis = false, useSelect, minimumCustomValue = 0, + onMouseOver, + onMouseOut, } ) { const spacingSizes = [ { name: 0, slug: '0', size: 0 }, @@ -70,6 +72,8 @@ export default function SpacingSizesControl( { useSelect, type: label, minimumCustomValue, + onMouseOver, + onMouseOut, }; return ( diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls.js index b8b71c22310b5..412dbad2030c9 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls.js @@ -11,6 +11,8 @@ export default function BoxInputControls( { spacingSizes, type, minimumCustomValue, + onMouseOver, + onMouseOut, } ) { // Filter sides if custom configuration provided, maintaining default order. const filteredSides = sides?.length @@ -38,6 +40,8 @@ export default function BoxInputControls( { spacingSizes={ spacingSizes } type={ type } minimumCustomValue={ minimumCustomValue } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ); } ) } diff --git a/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js b/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js index a6262873af553..0e155c421d6ab 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js +++ b/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js @@ -51,6 +51,8 @@ export default function SpacingInputControl( { isMixed = false, type, minimumCustomValue, + onMouseOver, + onMouseOut, } ) { // Treat value as a preset value if the passed in value matches the value of one of the spacingSizes. value = getPresetValueFromCustomValue( value, spacingSizes ); @@ -218,6 +220,8 @@ export default function SpacingInputControl( { { showCustomValueControl && ( <> onChange( getNewCustomValue( newSize ) ) } @@ -229,11 +233,12 @@ export default function SpacingInputControl( { label={ ariaLabel } hideLabelFromVision={ true } className="components-spacing-sizes-control__custom-value-input" - style={ { gridColumn: '1' } } size={ '__unstable-large' } /> @@ -293,6 +300,8 @@ export default function SpacingInputControl( { hideLabelFromVision={ true } __nextUnconstrainedWidth={ true } size={ '__unstable-large' } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ) } diff --git a/packages/block-editor/src/components/spacing-sizes-control/style.scss b/packages/block-editor/src/components/spacing-sizes-control/style.scss index 02731920c5e6f..4b60a744bfd90 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/style.scss +++ b/packages/block-editor/src/components/spacing-sizes-control/style.scss @@ -91,6 +91,7 @@ .components-spacing-sizes-control__custom-value-input { width: 124px; margin-top: 8px; + grid-column: 1; } .components-range-control { diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 1b33d471b1d73..3858f3983103a 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -543,7 +543,7 @@ class URLInput extends Component { !! suggestions.length ) { return ( - +
setIsMouseOver( true ); + const onMouseOut = () => setIsMouseOver( false ); + return { isMouseOver, onMouseOver, onMouseOut }; +} + /** * Inspector controls for dimensions support. * @@ -58,6 +65,8 @@ export function DimensionsPanel( props ) { const isDisabled = useIsDimensionsDisabled( props ); const isSupported = hasDimensionsSupport( props.name ); const spacingSizes = useSetting( 'spacing.spacingSizes' ); + const paddingMouseOver = useVisualizerMouseOver(); + const marginMouseOver = useVisualizerMouseOver(); if ( isDisabled || ! isSupported ) { return null; @@ -96,7 +105,11 @@ export function DimensionsPanel( props ) { isShownByDefault={ defaultSpacingControls?.padding } panelId={ props.clientId } > - + ) } { ! isMarginDisabled && ( @@ -109,7 +122,11 @@ export function DimensionsPanel( props ) { isShownByDefault={ defaultSpacingControls?.margin } panelId={ props.clientId } > - + ) } { ! isGapDisabled && ( @@ -126,8 +143,18 @@ export function DimensionsPanel( props ) { ) } - { ! isPaddingDisabled && } - { ! isMarginDisabled && } + { ! isPaddingDisabled && ( + + ) } + { ! isMarginDisabled && ( + + ) } ); } diff --git a/packages/block-editor/src/hooks/font-size.js b/packages/block-editor/src/hooks/font-size.js index f8fa94538f53a..6cb950afc4564 100644 --- a/packages/block-editor/src/hooks/font-size.js +++ b/packages/block-editor/src/hooks/font-size.js @@ -150,6 +150,7 @@ export function FontSizeEdit( props ) { onChange={ onChange } value={ fontSizeValue } withReset={ false } + withSlider size="__unstable-large" __nextHasNoMarginBottom /> diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 73d655c57c2ac..eeea8f3df2c8c 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -9,11 +9,7 @@ import { kebabCase } from 'lodash'; */ import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; -import { - getBlockDefaultClassName, - getBlockSupport, - hasBlockSupport, -} from '@wordpress/blocks'; +import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { Button, @@ -366,9 +362,8 @@ export const withLayoutStyles = createHigherOrderComponent( const layoutClasses = hasLayoutBlockSupport ? useLayoutClasses( block ) : null; - const selector = `.${ getBlockDefaultClassName( - name - ) }.wp-container-${ id }`; + // Higher specificity to override defaults from theme.json. + const selector = `.wp-container-${ id }.wp-container-${ id }`; const blockGapSupport = useSetting( 'spacing.blockGap' ); const hasBlockGapSupport = blockGapSupport !== null; @@ -413,7 +408,10 @@ export const withLayoutStyles = createHigherOrderComponent( />, element ) } - + ); } diff --git a/packages/block-editor/src/hooks/margin.js b/packages/block-editor/src/hooks/margin.js index 0e60717486d2b..d046685a23323 100644 --- a/packages/block-editor/src/hooks/margin.js +++ b/packages/block-editor/src/hooks/margin.js @@ -29,7 +29,7 @@ import { import { cleanEmptyObject } from './utils'; import BlockPopover from '../components/block-popover'; import SpacingSizesControl from '../components/spacing-sizes-control'; -import { getCustomValueFromPreset } from '../components/spacing-sizes-control/utils'; +import { getSpacingPresetCssVar } from '../components/spacing-sizes-control/utils'; /** * Determines if there is margin support. @@ -101,6 +101,8 @@ export function MarginEdit( props ) { name: blockName, attributes: { style }, setAttributes, + onMouseOver, + onMouseOut, } = props; const spacingSizes = useSetting( 'spacing.spacingSizes' ); @@ -148,6 +150,8 @@ export function MarginEdit( props ) { units={ units } allowReset={ false } splitOnAxis={ splitOnAxis } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ) } { spacingSizes?.length > 0 && ( @@ -159,6 +163,8 @@ export function MarginEdit( props ) { units={ units } allowReset={ false } splitOnAxis={ false } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ) } @@ -167,22 +173,21 @@ export function MarginEdit( props ) { } ); } -export function MarginVisualizer( { clientId, attributes } ) { +export function MarginVisualizer( { clientId, attributes, forceShow } ) { const margin = attributes?.style?.spacing?.margin; - const spacingSizes = useSetting( 'spacing.spacingSizes' ); const style = useMemo( () => { const marginTop = margin?.top - ? getCustomValueFromPreset( margin?.top, spacingSizes ) + ? getSpacingPresetCssVar( margin?.top ) : 0; const marginRight = margin?.right - ? getCustomValueFromPreset( margin?.right, spacingSizes ) + ? getSpacingPresetCssVar( margin?.right ) : 0; const marginBottom = margin?.bottom - ? getCustomValueFromPreset( margin?.bottom, spacingSizes ) + ? getSpacingPresetCssVar( margin?.bottom ) : 0; const marginLeft = margin?.left - ? getCustomValueFromPreset( margin?.left, spacingSizes ) + ? getSpacingPresetCssVar( margin?.left ) : 0; return { @@ -190,10 +195,10 @@ export function MarginVisualizer( { clientId, attributes } ) { borderRightWidth: marginRight, borderBottomWidth: marginBottom, borderLeftWidth: marginLeft, - top: marginTop !== 0 ? `calc(${ marginTop } * -1)` : 0, - right: marginRight !== 0 ? `calc(${ marginRight } * -1)` : 0, - bottom: marginBottom !== 0 ? `calc(${ marginBottom } * -1)` : 0, - left: marginLeft !== 0 ? `calc(${ marginLeft } * -1)` : 0, + top: marginTop ? `calc(${ marginTop } * -1)` : 0, + right: marginRight ? `calc(${ marginRight } * -1)` : 0, + bottom: marginBottom ? `calc(${ marginBottom } * -1)` : 0, + left: marginLeft ? `calc(${ marginLeft } * -1)` : 0, }; }, [ margin ] ); @@ -208,7 +213,7 @@ export function MarginVisualizer( { clientId, attributes } ) { }; useEffect( () => { - if ( ! isShallowEqual( margin, valueRef.current ) ) { + if ( ! isShallowEqual( margin, valueRef.current ) && ! forceShow ) { setIsActive( true ); valueRef.current = margin; @@ -220,9 +225,9 @@ export function MarginVisualizer( { clientId, attributes } ) { } return () => clearTimer(); - }, [ margin ] ); + }, [ margin, forceShow ] ); - if ( ! isActive ) { + if ( ! isActive && ! forceShow ) { return null; } diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index 46015b1e16463..007bc799f9bf8 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -29,10 +29,7 @@ import { import { cleanEmptyObject } from './utils'; import BlockPopover from '../components/block-popover'; import SpacingSizesControl from '../components/spacing-sizes-control'; -import { - getSpacingPresetCssVar, - isValueSpacingPreset, -} from '../components/spacing-sizes-control/utils'; +import { getSpacingPresetCssVar } from '../components/spacing-sizes-control/utils'; /** * Determines if there is padding support. * @@ -103,6 +100,8 @@ export function PaddingEdit( props ) { name: blockName, attributes: { style }, setAttributes, + onMouseOver, + onMouseOut, } = props; const spacingSizes = useSetting( 'spacing.spacingSizes' ); @@ -150,6 +149,8 @@ export function PaddingEdit( props ) { units={ units } allowReset={ false } splitOnAxis={ splitOnAxis } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ) } { spacingSizes?.length > 0 && ( @@ -161,6 +162,8 @@ export function PaddingEdit( props ) { units={ units } allowReset={ false } splitOnAxis={ splitOnAxis } + onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } /> ) } @@ -169,22 +172,22 @@ export function PaddingEdit( props ) { } ); } -export function PaddingVisualizer( { clientId, attributes } ) { +export function PaddingVisualizer( { clientId, attributes, forceShow } ) { const padding = attributes?.style?.spacing?.padding; const style = useMemo( () => { return { - borderTopWidth: isValueSpacingPreset( padding?.top ) + borderTopWidth: padding?.top ? getSpacingPresetCssVar( padding?.top ) - : padding?.top, - borderRightWidth: isValueSpacingPreset( padding?.right ) + : 0, + borderRightWidth: padding?.right ? getSpacingPresetCssVar( padding?.right ) - : padding?.right, - borderBottomWidth: isValueSpacingPreset( padding?.bottom ) + : 0, + borderBottomWidth: padding?.bottom ? getSpacingPresetCssVar( padding?.bottom ) - : padding?.bottom, - borderLeftWidth: isValueSpacingPreset( padding?.left ) + : 0, + borderLeftWidth: padding?.left ? getSpacingPresetCssVar( padding?.left ) - : padding?.left, + : 0, }; }, [ padding ] ); @@ -199,7 +202,7 @@ export function PaddingVisualizer( { clientId, attributes } ) { }; useEffect( () => { - if ( ! isShallowEqual( padding, valueRef.current ) ) { + if ( ! isShallowEqual( padding, valueRef.current ) && ! forceShow ) { setIsActive( true ); valueRef.current = padding; @@ -211,9 +214,9 @@ export function PaddingVisualizer( { clientId, attributes } ) { } return () => clearTimer(); - }, [ padding ] ); + }, [ padding, forceShow ] ); - if ( ! isActive ) { + if ( ! isActive && ! forceShow ) { return null; } diff --git a/packages/block-editor/src/hooks/test/align.js b/packages/block-editor/src/hooks/test/align.js index 3677b495910c4..d6a9b3aba65ce 100644 --- a/packages/block-editor/src/hooks/test/align.js +++ b/packages/block-editor/src/hooks/test/align.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import renderer, { act } from 'react-test-renderer'; +import { render, screen } from '@testing-library/react'; /** * WordPress dependencies @@ -12,10 +12,13 @@ import { registerBlockType, unregisterBlockType, } from '@wordpress/blocks'; +import { SlotFillProvider } from '@wordpress/components'; /** * Internal dependencies */ +import BlockControls from '../../components/block-controls'; +import BlockEdit from '../../components/block-edit'; import BlockEditorProvider from '../../components/provider'; import { getValidAlignments, @@ -31,6 +34,7 @@ describe( 'align', () => { save: noop, category: 'text', title: 'block title', + edit: ( { children } ) => <>{ children }, }; afterEach( () => { @@ -154,6 +158,12 @@ describe( 'align', () => { } ); describe( 'withToolbarControls', () => { + const componentProps = { + name: 'core/foo', + attributes: {}, + isSelected: true, + }; + it( 'should do nothing if no valid alignments', () => { registerBlockType( 'core/foo', blockSettings ); @@ -161,15 +171,21 @@ describe( 'align', () => { ( { wrapperProps } ) =>
); - const wrapper = renderer.create( - + render( + + + + + + ); - // When there's only one child, `rendered` in the tree is an object not an array. - expect( wrapper.toTree().rendered ).toBeInstanceOf( Object ); + + expect( + screen.queryByRole( 'button', { + name: 'Align', + expanded: false, + } ) + ).not.toBeInTheDocument(); } ); it( 'should render toolbar controls if valid alignments', () => { @@ -185,14 +201,21 @@ describe( 'align', () => { ( { wrapperProps } ) =>
); - const wrapper = renderer.create( - + render( + + + + + + ); - expect( wrapper.toTree().rendered ).toHaveLength( 2 ); + + expect( + screen.getAllByRole( 'button', { + name: 'Align', + expanded: false, + } ) + ).toHaveLength( 2 ); } ); } ); @@ -207,28 +230,27 @@ describe( 'align', () => { } ); const EnhancedComponent = withDataAlign( ( { wrapperProps } ) => ( -
+
+ { ! RichText.isEmpty( caption ) && ( + + ) } + + ); + }, +}; + +const v1 = { + attributes: blockAttributes, + save( { attributes: { url, caption, type, providerNameSlug } } ) { + if ( ! url ) { + return null; + } + + const embedClassName = classnames( 'wp-block-embed', { + [ `is-type-${ type }` ]: type, + [ `is-provider-${ providerNameSlug }` ]: providerNameSlug, + } ); + + return ( +
+ { `\n${ url }\n` /* URL needs to be on its own line. */ } + { ! RichText.isEmpty( caption ) && ( + + ) } +
+ ); }, -]; +}; + +const deprecated = [ v2, v1 ]; export default deprecated; diff --git a/packages/block-library/src/file/deprecated.js b/packages/block-library/src/file/deprecated.js index d0aed2a3868e8..ba053cb05019d 100644 --- a/packages/block-library/src/file/deprecated.js +++ b/packages/block-library/src/file/deprecated.js @@ -14,7 +14,7 @@ import { import { __, sprintf } from '@wordpress/i18n'; // Version of the file block without PR#43050 removing the translated aria-label. -const v2 = { +const v3 = { attributes: { id: { type: 'number', @@ -143,6 +143,134 @@ const v2 = { }, }; +// In #41239 the button was made an element button which added a `wp-element-button` classname +// to the download link element. +const v2 = { + attributes: { + id: { + type: 'number', + }, + href: { + type: 'string', + }, + fileId: { + type: 'string', + source: 'attribute', + selector: 'a:not([download])', + attribute: 'id', + }, + fileName: { + type: 'string', + source: 'html', + selector: 'a:not([download])', + }, + textLinkHref: { + type: 'string', + source: 'attribute', + selector: 'a:not([download])', + attribute: 'href', + }, + textLinkTarget: { + type: 'string', + source: 'attribute', + selector: 'a:not([download])', + attribute: 'target', + }, + showDownloadButton: { + type: 'boolean', + default: true, + }, + downloadButtonText: { + type: 'string', + source: 'html', + selector: 'a[download]', + }, + displayPreview: { + type: 'boolean', + }, + previewHeight: { + type: 'number', + default: 600, + }, + }, + supports: { + anchor: true, + align: true, + }, + save( { attributes } ) { + const { + href, + fileId, + fileName, + textLinkHref, + textLinkTarget, + showDownloadButton, + downloadButtonText, + displayPreview, + previewHeight, + } = attributes; + + const pdfEmbedLabel = RichText.isEmpty( fileName ) + ? __( 'PDF embed' ) + : sprintf( + /* translators: %s: filename. */ + __( 'Embed of %s.' ), + fileName + ); + + const hasFilename = ! RichText.isEmpty( fileName ); + + // Only output an `aria-describedby` when the element it's referring to is + // actually rendered. + const describedById = hasFilename ? fileId : undefined; + + return ( + href && ( +
+ { displayPreview && ( + <> + + + ) } + { hasFilename && ( + + + + ) } + { showDownloadButton && ( + + + + ) } + + ) + ); + }, +}; + // Version of the file block without PR#28062 accessibility fix. const v1 = { attributes: { @@ -255,6 +383,6 @@ const v1 = { }, }; -const deprecated = [ v2, v1 ]; +const deprecated = [ v3, v2, v1 ]; export default deprecated; diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index 3f96667b14745..35dd9807daddd 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { includes } from 'lodash'; - /** * WordPress dependencies */ @@ -92,7 +87,7 @@ const transforms = { } const { getMedia } = select( coreStore ); const media = getMedia( id ); - return !! media && includes( media.mime_type, 'audio' ); + return !! media && media.mime_type.includes( 'audio' ); }, transform: ( attributes ) => { return createBlock( 'core/audio', { @@ -112,7 +107,7 @@ const transforms = { } const { getMedia } = select( coreStore ); const media = getMedia( id ); - return !! media && includes( media.mime_type, 'video' ); + return !! media && media.mime_type.includes( 'video' ); }, transform: ( attributes ) => { return createBlock( 'core/video', { @@ -132,7 +127,7 @@ const transforms = { } const { getMedia } = select( coreStore ); const media = getMedia( id ); - return !! media && includes( media.mime_type, 'image' ); + return !! media && media.mime_type.includes( 'image' ); }, transform: ( attributes ) => { return createBlock( 'core/image', { diff --git a/packages/block-library/src/gallery/deprecated.js b/packages/block-library/src/gallery/deprecated.js index a2843ecf6e3b4..313b700d6b368 100644 --- a/packages/block-library/src/gallery/deprecated.js +++ b/packages/block-library/src/gallery/deprecated.js @@ -2,12 +2,16 @@ * External dependencies */ import classnames from 'classnames'; -import { map, some } from 'lodash'; +import { map } from 'lodash'; /** * WordPress dependencies */ -import { RichText, useBlockProps } from '@wordpress/block-editor'; +import { + RichText, + useBlockProps, + useInnerBlocksProps, +} from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; @@ -127,6 +131,127 @@ export function getImageBlock( image, sizeSlug, linkTo ) { } ); } +// In #41140 support was added to global styles for caption elements which added a `wp-element-caption` classname +// to the gallery figcaption element. +const v7 = { + attributes: { + images: { + type: 'array', + default: [], + source: 'query', + selector: '.blocks-gallery-item', + query: { + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + fullUrl: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'data-full-url', + }, + link: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'data-link', + }, + alt: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + }, + id: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'data-id', + }, + caption: { + type: 'string', + source: 'html', + selector: '.blocks-gallery-item__caption', + }, + }, + }, + ids: { + type: 'array', + items: { + type: 'number', + }, + default: [], + }, + shortCodeTransforms: { + type: 'array', + default: [], + items: { + type: 'object', + }, + }, + columns: { + type: 'number', + minimum: 1, + maximum: 8, + }, + caption: { + type: 'string', + source: 'html', + selector: '.blocks-gallery-caption', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + fixedHeight: { + type: 'boolean', + default: true, + }, + linkTarget: { + type: 'string', + }, + linkTo: { + type: 'string', + }, + sizeSlug: { + type: 'string', + default: 'large', + }, + allowResize: { + type: 'boolean', + default: false, + }, + }, + save( { attributes } ) { + const { caption, columns, imageCrop } = attributes; + + const className = classnames( 'has-nested-images', { + [ `columns-${ columns }` ]: columns !== undefined, + [ `columns-default` ]: columns === undefined, + 'is-cropped': imageCrop, + } ); + const blockProps = useBlockProps.save( { className } ); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + + return ( +
+ { innerBlocksProps.children } + { ! RichText.isEmpty( caption ) && ( + + ) } +
+ ); + }, +}; + const v6 = { attributes: { images: { @@ -803,7 +928,7 @@ const v2 = { images.length > 0 && ( ( ! ids && images ) || ( ids && images && ids.length !== images.length ) || - some( images, ( id, index ) => { + images.some( ( id, index ) => { if ( ! id && ids[ index ] !== null ) { return true; } @@ -984,4 +1109,4 @@ const v1 = { }, }; -export default [ v6, v5, v4, v3, v2, v1 ]; +export default [ v7, v6, v5, v4, v3, v2, v1 ]; diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js index 9a551b6e0d9f7..e6176cc8a7256 100644 --- a/packages/block-library/src/gallery/gallery.js +++ b/packages/block-library/src/gallery/gallery.js @@ -26,6 +26,7 @@ export const Gallery = ( props ) => { mediaPlaceholder, insertBlocksAfter, blockProps, + __unstableLayoutClassNames: layoutClassNames, } = props; const { align, columns, caption, imageCrop } = attributes; @@ -42,6 +43,7 @@ export const Gallery = ( props ) => { { ...innerBlocksProps } className={ classnames( blockProps.className, + layoutClassNames, 'blocks-gallery-grid', { [ `align${ align }` ]: align, diff --git a/packages/block-library/src/gallery/index.php b/packages/block-library/src/gallery/index.php index 7bc5e2821b796..ff1fb8ef42d89 100644 --- a/packages/block-library/src/gallery/index.php +++ b/packages/block-library/src/gallery/index.php @@ -76,13 +76,10 @@ function block_core_gallery_render( $attributes, $content ) { } } - $class = wp_unique_id( 'wp-block-gallery-' ); - $content = preg_replace( - '/' . preg_quote( 'class="', '/' ) . '/', - 'class="' . $class . ' ', - $content, - 1 - ); + $unique_gallery_classname = wp_unique_id( 'wp-block-gallery-' ); + $processed_content = new WP_HTML_Tag_Processor( $content ); + $processed_content->next_tag(); + $processed_content->add_class( $unique_gallery_classname ); // --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default // gap on the gallery. @@ -102,10 +99,22 @@ function block_core_gallery_render( $attributes, $content ) { } // Set the CSS variable to the column value, and the `gap` property to the combined gap value. - $style = '.wp-block-gallery.' . $class . '{ --wp--style--unstable-gallery-gap: ' . $gap_column . '; gap: ' . $gap_value . '}'; + $gallery_styles = array(); + $gallery_styles[] = array( + 'selector' => ".wp-block-gallery.{$unique_gallery_classname}", + 'declarations' => array( + '--wp--style--unstable-gallery-gap' => $gap_column, + 'gap' => $gap_value, + ), + ); - wp_enqueue_block_support_styles( $style, 11 ); - return $content; + gutenberg_style_engine_get_stylesheet_from_css_rules( + $gallery_styles, + array( + 'context' => 'block-supports', + ) + ); + return (string) $processed_content; } /** * Registers the `core/gallery` block on server. diff --git a/packages/block-library/src/gallery/use-image-sizes.js b/packages/block-library/src/gallery/use-image-sizes.js index 877bacb67dd13..84c7ab59749d9 100644 --- a/packages/block-library/src/gallery/use-image-sizes.js +++ b/packages/block-library/src/gallery/use-image-sizes.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, some } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -57,9 +57,10 @@ export default function useImageSizes( images, isSelected, getSettings ) { }; }, {} ); } + const resizedImageSizes = Object.values( resizedImages ); return imageSizes .filter( ( { slug } ) => - some( resizedImages, ( sizes ) => sizes[ slug ] ) + resizedImageSizes.some( ( sizes ) => sizes[ slug ] ) ) .map( ( { name, slug } ) => ( { value: slug, label: name } ) ); } diff --git a/packages/block-library/src/gallery/v1/edit.js b/packages/block-library/src/gallery/v1/edit.js index e6619baa945c6..7f3852903aa23 100644 --- a/packages/block-library/src/gallery/v1/edit.js +++ b/packages/block-library/src/gallery/v1/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, find, get, isEmpty, map, reduce, some } from 'lodash'; +import { filter, find, get, isEmpty, map, reduce } from 'lodash'; /** * WordPress dependencies @@ -302,9 +302,10 @@ function GalleryEdit( props ) { } function getImagesSizeOptions() { + const resizedImageSizes = Object.values( resizedImages ); return map( filter( imageSizes, ( { slug } ) => - some( resizedImages, ( sizes ) => sizes[ slug ] ) + resizedImageSizes.some( ( sizes ) => sizes[ slug ] ) ), ( { name, slug } ) => ( { value: slug, label: name } ) ); diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 9d43c4339ba1f..efe0d0ede9ebc 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -35,7 +35,12 @@ const htmlElementMessages = { ), }; -function GroupEdit( { attributes, setAttributes, clientId } ) { +function GroupEdit( { + attributes, + setAttributes, + clientId, + __unstableLayoutClassNames: layoutClassNames, +} ) { const { hasInnerBlocks, themeSupportsLayout } = useSelect( ( select ) => { const { getBlock, getSettings } = select( blockEditorStore ); @@ -55,7 +60,9 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { const { type = 'default' } = usedLayout; const layoutSupportEnabled = themeSupportsLayout || type === 'flex'; - const blockProps = useBlockProps(); + const blockProps = useBlockProps( { + className: ! layoutSupportEnabled ? layoutClassNames : null, + } ); const innerBlocksProps = useInnerBlocksProps( layoutSupportEnabled @@ -67,6 +74,7 @@ function GroupEdit( { attributes, setAttributes, clientId } ) { ? undefined : InnerBlocks.ButtonBlockAppender, __experimentalLayout: layoutSupportEnabled ? usedLayout : undefined, + __unstableDisableLayoutClassNames: ! layoutSupportEnabled, } ); diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index c06abb7eaf4fc..8a9881fa95c71 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -878,7 +878,7 @@ export class ImageEdit extends Component { { - if ( showCaption && ! caption ) { - captionRef.current?.focus(); - } - }, [ caption, showCaption ] ); + const captionRef = useCallback( + ( node ) => { + if ( node && ! caption ) { + node.focus(); + } + }, + [ caption ] + ); // Get naturalWidth and naturalHeight from image ref, and fall back to loaded natural // width and height. This resolves an issue in Safari where the loaded natural @@ -343,7 +351,11 @@ export default function Image( { } } icon={ captionIcon } isPressed={ showCaption } - label={ __( 'Caption' ) } + label={ + showCaption + ? __( 'Remove caption' ) + : __( 'Add caption' ) + } /> ) } { ! multiImageSelection && ! isEditingImage && ( diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index cab44563248c4..3671ec555e600 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, includes, pickBy } from 'lodash'; +import { get, pickBy } from 'lodash'; import classnames from 'classnames'; /** @@ -196,7 +196,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { } ); // We do nothing if the category is not selected // from suggestions. - if ( includes( allCategories, null ) ) { + if ( allCategories.includes( null ) ) { return false; } setAttributes( { categories: allCategories } ); diff --git a/packages/block-library/src/list-item/hooks/use-outdent-list-item.js b/packages/block-library/src/list-item/hooks/use-outdent-list-item.js index d4c034b06e813..870aeb5088f80 100644 --- a/packages/block-library/src/list-item/hooks/use-outdent-list-item.js +++ b/packages/block-library/src/list-item/hooks/use-outdent-list-item.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { castArray } from 'lodash'; - /** * WordPress dependencies */ @@ -61,7 +56,9 @@ export default function useOutdentListItem( clientId ) { return [ canOutdent, useCallback( ( clientIds = getSelectedBlockClientIds() ) => { - clientIds = castArray( clientIds ); + if ( ! Array.isArray( clientIds ) ) { + clientIds = [ clientIds ]; + } if ( ! clientIds.length ) return; diff --git a/packages/block-library/src/list/utils.js b/packages/block-library/src/list/utils.js index 9603c1d08e0f0..a80ce456e29ab 100644 --- a/packages/block-library/src/list/utils.js +++ b/packages/block-library/src/list/utils.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createBlock } from '@wordpress/blocks'; +import { createBlock, rawHandler } from '@wordpress/blocks'; export function createListBlockFromDOMElement( listElement ) { const listAttributes = { @@ -70,15 +70,7 @@ export function migrateToListV2( attributes ) { list.setAttribute( 'type', type ); } - const listBlock = createListBlockFromDOMElement( list ); + const [ listBlock ] = rawHandler( { HTML: list.outerHTML } ); - const { values: omittedValues, ...restAttributes } = attributes; - - return [ - { - ...restAttributes, - ...listBlock.attributes, - }, - listBlock.innerBlocks, - ]; + return [ listBlock.attributes, listBlock.innerBlocks ]; } diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index fdf72d43a60aa..1b1b530dcdcdf 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -2,7 +2,8 @@ * External dependencies */ import classnames from 'classnames'; -import { escape, unescape } from 'lodash'; +import escapeHtml from 'escape-html'; +import { unescape } from 'lodash'; /** * WordPress dependencies @@ -262,8 +263,8 @@ export const updateNavigationLinkBlockAttributes = ( // - https://github.com/WordPress/gutenberg/pull/41063 // - https://github.com/WordPress/gutenberg/pull/18617. const label = useNewLabel - ? escape( newLabel ) - : originalLabel || escape( newUrlWithoutHttp ); + ? 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( '-', '_' ); @@ -846,7 +847,7 @@ export default function NavigationLinkEdit( { ) } { isLinkOpen && ( setIsLinkOpen( false ) } anchor={ popoverAnchor } shift diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index 77030e4ec4746..0f91313289d0b 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { escape, without } from 'lodash'; +import escapeHtml from 'escape-html'; /** * WordPress dependencies @@ -248,8 +248,8 @@ export const updateNavigationLinkBlockAttributes = ( normalizedTitle !== normalizedURL && originalLabel !== title; const label = escapeTitle - ? escape( title ) - : originalLabel || escape( normalizedURL ); + ? escapeHtml( title ) + : originalLabel || escapeHtml( normalizedURL ); // 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( '-', '_' ); @@ -486,7 +486,9 @@ export default function NavigationSubmenuEdit( { const innerBlocksColors = getColors( context, true ); const allowedBlocks = isAtMaxNesting - ? without( ALLOWED_BLOCKS, 'core/navigation-submenu' ) + ? ALLOWED_BLOCKS.filter( + ( blockName ) => blockName !== 'core/navigation-submenu' + ) : ALLOWED_BLOCKS; const innerBlocksProps = useInnerBlocksProps( @@ -631,7 +633,7 @@ export default function NavigationSubmenuEdit( { } { ! openSubmenusOnClick && isLinkOpen && ( setIsLinkOpen( false ) } anchor={ popoverAnchor } shift diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 8f26e1c57cc3a..918b609f92a0e 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -272,7 +272,7 @@ function Navigation( { fallbackNavigationMenus?.length > 0 || classicMenus?.length !== 1 ) { - return false; + return; } // If there's non fallback navigation menus and @@ -481,24 +481,22 @@ function Navigation( { // Prompt the user to publish the menu they have set as a draft const isDraftNavigationMenu = navigationMenu?.status === 'draft'; - useEffect( async () => { + useEffect( () => { hideMenuAutoPublishDraftNotice(); - if ( ! isDraftNavigationMenu ) return; - try { - await editEntityRecord( - 'postType', - 'wp_navigation', - navigationMenu?.id, - { - status: 'publish', - }, - { throwOnError: true } - ); - } catch { + if ( ! isDraftNavigationMenu ) { + return; + } + editEntityRecord( + 'postType', + 'wp_navigation', + navigationMenu?.id, + { status: 'publish' }, + { throwOnError: true } + ).catch( () => { showMenuAutoPublishDraftNotice( __( 'Error occurred while publishing the navigation menu.' ) ); - } + } ); }, [ isDraftNavigationMenu, navigationMenu ] ); const stylingInspectorControls = ( diff --git a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js index 892a86976412b..d656d3fb23f94 100644 --- a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js +++ b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js @@ -172,8 +172,15 @@ export default function UnsavedInnerBlocks( { return ( <> - - { isSaving && } + { isSaving ? ( + + ) : ( + + ) } ); } diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 1658c90fa38f1..f669b39cb6586 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -261,10 +261,6 @@ $color-control-label-height: 20px; } } -.wp-block-navigation-placeholder .components-spinner { - margin-top: 0; -} - // Unselected state. .wp-block-navigation-placeholder__preview { display: flex; @@ -567,6 +563,10 @@ body.editor-styles-wrapper padding: $grid-unit-10 $grid-unit-15; } +.wp-block-navigation .wp-block-navigation__uncontrolled-inner-blocks-loading-indicator { + margin-top: 0; +} + @keyframes fadeouthalf { 0% { opacity: 1; diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 0a00d7a779d31..45a30c644ed65 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -359,7 +359,9 @@ button.wp-block-navigation-item__content { // Provide a default padding for submenus who should always have some, regardless of the top level menu items. :where(.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item a:not(.wp-element-button)), -:where(.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-submenu a:not(.wp-element-button)) { +:where(.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-submenu a:not(.wp-element-button)), +:where(.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-submenu button.wp-block-navigation-item__content), +:where(.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-pages-list__item button.wp-block-navigation-item__content) { padding: 0.5em 1em; } diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index aa15f913ddfa9..71cbfa437a1f9 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -52,50 +52,64 @@ export default function PageListEdit( { context, clientId } ) { style: { ...context.style?.color }, } ); - return ( - <> - { allowConvertToLinks && ( - - - { __( 'Edit' ) } - - - ) } - { allowConvertToLinks && isOpen && ( - - ) } - { ! hasResolvedPages && ( + const getBlockContent = () => { + if ( ! hasResolvedPages ) { + return (
- ) } + ); + } - { hasResolvedPages && totalPages === null && ( + if ( totalPages === null ) { + return (
{ __( 'Page List: Cannot retrieve Pages.' ) }
- ) } + ); + } - { totalPages === 0 && ( + if ( totalPages === 0 ) { + return (
{ __( 'Page List: Cannot retrieve Pages.' ) }
- ) } - { totalPages > 0 && ( + ); + } + + if ( totalPages > 0 ) { + return (
+ ); + } + }; + + return ( + <> + { allowConvertToLinks && ( + + + { __( 'Edit' ) } + + ) } + { allowConvertToLinks && isOpen && ( + + ) } + + { getBlockContent() } ); } diff --git a/packages/block-library/src/post-comments-count/block.json b/packages/block-library/src/post-comments-count/block.json index 150293b29c986..a30920fbbee3e 100644 --- a/packages/block-library/src/post-comments-count/block.json +++ b/packages/block-library/src/post-comments-count/block.json @@ -22,6 +22,10 @@ "text": true } }, + "spacing": { + "margin": true, + "padding": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/post-comments-form/block.json b/packages/block-library/src/post-comments-form/block.json index 47cc0a443baee..5ed4122d1147b 100644 --- a/packages/block-library/src/post-comments-form/block.json +++ b/packages/block-library/src/post-comments-form/block.json @@ -22,6 +22,10 @@ "text": true } }, + "spacing": { + "margin": true, + "padding": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/post-comments-form/style.scss b/packages/block-library/src/post-comments-form/style.scss index 4e70906063d6f..807bcff416fb0 100644 --- a/packages/block-library/src/post-comments-form/style.scss +++ b/packages/block-library/src/post-comments-form/style.scss @@ -1,4 +1,7 @@ .wp-block-post-comments-form { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; + &[style*="font-weight"] :where(.comment-reply-title) { font-weight: inherit; } diff --git a/packages/block-library/src/post-comments-link/block.json b/packages/block-library/src/post-comments-link/block.json index 3df8788ee5d76..29b4a2f8f5445 100644 --- a/packages/block-library/src/post-comments-link/block.json +++ b/packages/block-library/src/post-comments-link/block.json @@ -23,6 +23,10 @@ "link": true } }, + "spacing": { + "margin": true, + "padding": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/post-content/edit.js b/packages/block-library/src/post-content/edit.js index 1949553b2540f..e190101aa0515 100644 --- a/packages/block-library/src/post-content/edit.js +++ b/packages/block-library/src/post-content/edit.js @@ -84,8 +84,8 @@ function Content( props ) { ); } -function Placeholder() { - const blockProps = useBlockProps(); +function Placeholder( { layoutClassNames } ) { + const blockProps = useBlockProps( { className: layoutClassNames } ); return (

@@ -118,7 +118,11 @@ function RecursionError() { ); } -export default function PostContentEdit( { context, attributes } ) { +export default function PostContentEdit( { + context, + attributes, + __unstableLayoutClassNames: layoutClassNames, +} ) { const { postId: contextPostId, postType: contextPostType } = context; const { layout = {} } = attributes; const hasAlreadyRendered = useHasRecursion( contextPostId ); @@ -132,7 +136,7 @@ export default function PostContentEdit( { context, attributes } ) { { contextPostId && contextPostType ? ( ) : ( - + ) } ); diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js index 9a9c6030b8e22..1f3ac876e4d45 100644 --- a/packages/block-library/src/pullquote/deprecated.js +++ b/packages/block-library/src/pullquote/deprecated.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, includes } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -140,7 +140,7 @@ const v4 = { className, } = attributes; - const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + const isSolidColorStyle = className?.includes( SOLID_COLOR_CLASS ); let figureClasses, figureStyles; @@ -206,7 +206,7 @@ const v4 = { customTextColor, ...attributes } ) { - const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + const isSolidColorStyle = className?.includes( SOLID_COLOR_CLASS ); let style; if ( customMainColor ) { @@ -270,7 +270,7 @@ const v3 = { figureStyle, } = attributes; - const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + const isSolidColorStyle = className?.includes( SOLID_COLOR_CLASS ); let figureClasses, figureStyles; @@ -345,7 +345,7 @@ const v3 = { customTextColor, ...attributes } ) { - const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + const isSolidColorStyle = className?.includes( SOLID_COLOR_CLASS ); let style; if ( customMainColor ) { @@ -416,7 +416,7 @@ const v2 = { citation, className, } = attributes; - const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + const isSolidColorStyle = className?.includes( SOLID_COLOR_CLASS ); let figureClass, figureStyles; // Is solid color style @@ -484,7 +484,7 @@ const v2 = { customTextColor, ...attributes } ) { - const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + const isSolidColorStyle = className?.includes( SOLID_COLOR_CLASS ); let style = {}; if ( customMainColor ) { diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 377788d281d3d..a3db69287f8f7 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -363,7 +363,7 @@ export default function SearchEdit( { widthUnit: newUnit, } ); } } - style={ { maxWidth: 80 } } + __unstableInputWidth={ '80px' } value={ `${ width }${ widthUnit }` } units={ units } /> diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 20cbe4ef927d1..79e05300d6382 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { includes, pick } from 'lodash'; +import { pick } from 'lodash'; /** * WordPress dependencies @@ -71,7 +71,7 @@ const SiteLogo = ( { } ) => { const clientWidth = useClientWidth( containerRef, [ align ] ); const isLargeViewport = useViewportMatch( 'medium' ); - const isWideAligned = includes( [ 'wide', 'full' ], align ); + const isWideAligned = [ 'wide', 'full' ].includes( align ); const isResizable = ! isWideAligned && isLargeViewport; const [ { naturalWidth, naturalHeight }, setNaturalSize ] = useState( {} ); const [ isEditingImage, setIsEditingImage ] = useState( false ); diff --git a/packages/block-library/src/site-tagline/edit.js b/packages/block-library/src/site-tagline/edit.js index 5c8257d11c3fd..1ce13c6eb43c4 100644 --- a/packages/block-library/src/site-tagline/edit.js +++ b/packages/block-library/src/site-tagline/edit.js @@ -6,8 +6,8 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; -import { useEntityProp, store as coreStore } from '@wordpress/core-data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; import { AlignmentControl, useBlockProps, @@ -23,34 +23,43 @@ export default function SiteTaglineEdit( { insertBlocksAfter, } ) { const { textAlign } = attributes; - const [ siteTagline, setSiteTagline ] = useEntityProp( - 'root', - 'site', - 'description' - ); - const { canUserEdit, readOnlySiteTagLine } = useSelect( ( select ) => { - const { canUser, getEntityRecord } = select( coreStore ); - const siteData = getEntityRecord( 'root', '__unstableBase' ); + const { canUserEdit, tagline } = useSelect( ( select ) => { + const { canUser, getEntityRecord, getEditedEntityRecord } = + select( coreStore ); + const canEdit = canUser( 'update', 'settings' ); + const settings = canEdit ? getEditedEntityRecord( 'root', 'site' ) : {}; + const readOnlySettings = getEntityRecord( 'root', '__unstableBase' ); + return { canUserEdit: canUser( 'update', 'settings' ), - readOnlySiteTagLine: siteData?.description, + tagline: canEdit + ? settings?.description + : readOnlySettings?.description, }; }, [] ); + + const { editEntityRecord } = useDispatch( coreStore ); + + function setTagline( newTagline ) { + editEntityRecord( 'root', 'site', undefined, { + description: newTagline, + } ); + } + const blockProps = useBlockProps( { className: classnames( { [ `has-text-align-${ textAlign }` ]: textAlign, - 'wp-block-site-tagline__placeholder': - ! canUserEdit && ! readOnlySiteTagLine, + 'wp-block-site-tagline__placeholder': ! canUserEdit && ! tagline, } ), } ); const siteTaglineContent = canUserEdit ? ( insertBlocksAfter( createBlock( getDefaultBlockName() ) ) @@ -58,9 +67,7 @@ export default function SiteTaglineEdit( { { ...blockProps } /> ) : ( -

- { readOnlySiteTagLine || __( 'Site Tagline placeholder' ) } -

+

{ tagline || __( 'Site Tagline placeholder' ) }

); return ( <> diff --git a/packages/block-library/src/site-title/edit/index.js b/packages/block-library/src/site-title/edit/index.js index bbe91cea95bbb..5980dff6d8819 100644 --- a/packages/block-library/src/site-title/edit/index.js +++ b/packages/block-library/src/site-title/edit/index.js @@ -6,8 +6,8 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; -import { useEntityProp, store as coreStore } from '@wordpress/core-data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { RichText, @@ -31,21 +31,31 @@ export default function SiteTitleEdit( { insertBlocksAfter, } ) { const { level, textAlign, isLink, linkTarget } = attributes; - const [ title, setTitle ] = useEntityProp( 'root', 'site', 'title' ); - const { canUserEdit, readOnlyTitle } = useSelect( ( select ) => { - const { canUser, getEntityRecord } = select( coreStore ); - const siteData = getEntityRecord( 'root', '__unstableBase' ); + const { canUserEdit, title } = useSelect( ( select ) => { + const { canUser, getEntityRecord, getEditedEntityRecord } = + select( coreStore ); + const canEdit = canUser( 'update', 'settings' ); + const settings = canEdit ? getEditedEntityRecord( 'root', 'site' ) : {}; + const readOnlySettings = getEntityRecord( 'root', '__unstableBase' ); + return { - canUserEdit: canUser( 'update', 'settings' ), - readOnlyTitle: decodeEntities( siteData?.name ), + canUserEdit: canEdit, + title: canEdit ? settings?.title : readOnlySettings?.name, }; }, [] ); + const { editEntityRecord } = useDispatch( coreStore ); + + function setTitle( newTitle ) { + editEntityRecord( 'root', 'site', undefined, { + title: newTitle, + } ); + } + const TagName = level === 0 ? 'p' : `h${ level }`; const blockProps = useBlockProps( { className: classnames( { [ `has-text-align-${ textAlign }` ]: textAlign, - 'wp-block-site-title__placeholder': - ! canUserEdit && ! readOnlyTitle, + 'wp-block-site-title__placeholder': ! canUserEdit && ! title, } ), } ); const siteTitleContent = canUserEdit ? ( @@ -71,10 +81,14 @@ export default function SiteTitleEdit( { href="#site-title-pseudo-link" onClick={ ( event ) => event.preventDefault() } > - { readOnlyTitle || __( 'Site Title placeholder' ) } + { decodeEntities( title ) || + __( 'Site Title placeholder' ) } ) : ( - { title || readOnlyTitle } + + { decodeEntities( title ) || + __( 'Site Title placeholder' ) } + ) } ); diff --git a/packages/block-library/src/social-link/block.json b/packages/block-library/src/social-link/block.json index 90a3f270d738d..5f64b7dcf777c 100644 --- a/packages/block-library/src/social-link/block.json +++ b/packages/block-library/src/social-link/block.json @@ -16,6 +16,9 @@ }, "label": { "type": "string" + }, + "rel": { + "type": "string" } }, "usesContext": [ diff --git a/packages/block-library/src/social-link/edit.js b/packages/block-library/src/social-link/edit.js index 7e6a51618a897..fab49838afa2e 100644 --- a/packages/block-library/src/social-link/edit.js +++ b/packages/block-library/src/social-link/edit.js @@ -66,7 +66,7 @@ const SocialLinkEdit = ( { isSelected, setAttributes, } ) => { - const { url, service, label } = attributes; + const { url, service, label, rel } = attributes; const { showLabels, iconColorValue, iconBackgroundColorValue } = context; const [ showURLPopover, setPopover ] = useState( false ); const classes = classNames( 'wp-social-link', 'wp-social-link-' + service, { @@ -113,6 +113,13 @@ const SocialLinkEdit = ( { + + setAttributes( { rel: value } ) } + /> +
  • - - - ); -}; - -export const Default = _default.bind( {} ); -Default.args = { - disableCustomColors: false, - enableAlpha: true, - enableStyle: true, - defaultBorder: { - color: '#72aee6', - style: 'dashed', - width: '1px', - }, - __next36pxDefaultSize: false, - popoverPlacement: 'right-start', -}; - -const WrapperView = styled.div` - max-width: 280px; - padding: 16px; -`; - -const Separator = styled.hr` - margin-top: 100px; - border-color: #ddd; - border-style: solid; - border-bottom: none; -`; - -const HelpText = styled.p` - color: #aaa; - font-size: 0.9em; -`; diff --git a/packages/components/src/border-box-control/stories/index.tsx b/packages/components/src/border-box-control/stories/index.tsx new file mode 100644 index 0000000000000..b391c2f00f592 --- /dev/null +++ b/packages/components/src/border-box-control/stories/index.tsx @@ -0,0 +1,90 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { ComponentProps } from 'react'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import Popover from '../../popover'; +import { BorderBoxControl } from '../'; +import { Provider as SlotFillProvider } from '../../slot-fill'; + +const meta: ComponentMeta< typeof BorderBoxControl > = { + title: 'Components (Experimental)/BorderBoxControl', + component: BorderBoxControl, + argTypes: { + onChange: { action: 'onChange' }, + value: { control: { type: null } }, + }, + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +// Available border colors. +const colors = [ + { name: 'Blue 20', color: '#72aee6' }, + { name: 'Blue 40', color: '#3582c4' }, + { name: 'Red 40', color: '#e65054' }, + { name: 'Red 70', color: '#8a2424' }, + { name: 'Yellow 10', color: '#f2d675' }, + { name: 'Yellow 40', color: '#bd8600' }, +]; + +const Template: ComponentStory< typeof BorderBoxControl > = ( props ) => { + const { onChange, ...otherProps } = props; + const [ borders, setBorders ] = useState< typeof props[ 'value' ] >(); + + const onChangeMerged: ComponentProps< + typeof BorderBoxControl + >[ 'onChange' ] = ( newBorders ) => { + setBorders( newBorders ); + onChange( newBorders ); + }; + + return ( + + +
    +

    + The BorderBoxControl is intended to be used within a component + that will provide reset controls. The button below is only for + convenience. +

    + + { /* @ts-expect-error Ignore until Popover.Slot is converted to TS */ } + +
    + ); +}; +export const Default = Template.bind( {} ); +Default.args = { + colors, + label: 'Borders', +}; diff --git a/packages/components/src/border-box-control/styles.ts b/packages/components/src/border-box-control/styles.ts index e5bb4ce638acd..3fba981080a51 100644 --- a/packages/components/src/border-box-control/styles.ts +++ b/packages/components/src/border-box-control/styles.ts @@ -7,29 +7,33 @@ import { css } from '@emotion/react'; * Internal dependencies */ import { COLORS, CONFIG, rtl } from '../utils'; -import { space } from '../ui/utils/space'; import type { Border } from '../border-control/types'; import type { Borders } from './types'; -export const BorderBoxControl = css``; +export const borderBoxControl = css``; -export const LinkedBorderControl = css` +export const linkedBorderControl = () => css` flex: 1; + ${ rtl( { marginRight: '24px' } )() } `; -export const BorderBoxControlLinkedButton = ( - __next36pxDefaultSize?: boolean +export const wrapper = css` + position: relative; +`; + +export const borderBoxControlLinkedButton = ( + size?: 'default' | '__unstable-large' ) => { return css` - flex: 0; - flex-basis: 24px; + position: absolute; + top: ${ size === '__unstable-large' ? '8px' : '3px' }; + ${ rtl( { right: 0 } )() } line-height: 0; - margin-top: ${ __next36pxDefaultSize ? '6px' : '3px' }; `; }; -const BorderBoxStyleWithFallback = ( border?: Border ) => { +const borderBoxStyleWithFallback = ( border?: Border ) => { const { color = COLORS.gray[ 200 ], style = 'solid', @@ -46,36 +50,38 @@ const BorderBoxStyleWithFallback = ( border?: Border ) => { export const borderBoxControlVisualizer = ( borders?: Borders, - __next36pxDefaultSize?: boolean + size?: 'default' | '__unstable-large' ) => { return css` position: absolute; - top: ${ __next36pxDefaultSize ? '18px' : '15px' }; - right: 30px; - bottom: ${ __next36pxDefaultSize ? '18px' : '15px' }; - left: 30px; - border-top: ${ BorderBoxStyleWithFallback( borders?.top ) }; - border-bottom: ${ BorderBoxStyleWithFallback( borders?.bottom ) }; + top: ${ size === '__unstable-large' ? '20px' : '15px' }; + right: ${ size === '__unstable-large' ? '39px' : '29px' }; + bottom: ${ size === '__unstable-large' ? '20px' : '15px' }; + left: ${ size === '__unstable-large' ? '39px' : '29px' }; + border-top: ${ borderBoxStyleWithFallback( borders?.top ) }; + border-bottom: ${ borderBoxStyleWithFallback( borders?.bottom ) }; ${ rtl( { - borderLeft: BorderBoxStyleWithFallback( borders?.left ), + borderLeft: borderBoxStyleWithFallback( borders?.left ), } )() } ${ rtl( { - borderRight: BorderBoxStyleWithFallback( borders?.right ), + borderRight: borderBoxStyleWithFallback( borders?.right ), } )() } `; }; -export const borderBoxControlSplitControls = () => css` +export const borderBoxControlSplitControls = ( + size?: 'default' | '__unstable-large' +) => css` position: relative; flex: 1; - ${ rtl( { marginRight: space( 3 ) }, { marginLeft: space( 3 ) } )() } + width: ${ size === '__unstable-large' ? undefined : '80%' }; `; -export const CenteredBorderControl = css` +export const centeredBorderControl = css` grid-column: span 2; margin: 0 auto; `; export const rightBorderControl = () => css` - ${ rtl( { marginLeft: 'auto' }, { marginRight: 'auto' } )() } + ${ rtl( { marginLeft: 'auto' } )() } `; diff --git a/packages/components/src/border-box-control/test/index.js b/packages/components/src/border-box-control/test/index.js index d22ecdf37124d..af805aa4bde11 100644 --- a/packages/components/src/border-box-control/test/index.js +++ b/packages/components/src/border-box-control/test/index.js @@ -1,7 +1,8 @@ /** * External dependencies */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * Internal dependencies @@ -48,40 +49,22 @@ const props = { const toggleLabelRegex = /Border color( and style)* picker/; const colorPickerRegex = /Border color picker/; -const renderBorderBoxControl = ( customProps ) => { - return render( ); -}; - -const clickButton = ( name ) => { - fireEvent.click( screen.getByRole( 'button', { name } ) ); -}; - -const queryButton = ( name ) => { - return screen.queryByRole( 'button', { name } ); -}; - -const updateLinkedWidthInput = ( value ) => { - const widthInput = screen.getByRole( 'spinbutton' ); - widthInput.focus(); - fireEvent.change( widthInput, { target: { value } } ); -}; - -const updateSplitWidthInput = ( value, index = 0 ) => { - const splitInputs = screen.getAllByRole( 'spinbutton' ); - splitInputs[ index ].focus(); - fireEvent.change( splitInputs[ index ], { target: { value } } ); -}; - describe( 'BorderBoxControl', () => { describe( 'Linked view rendering', () => { it( 'should render correctly when no value provided', () => { - renderBorderBoxControl(); + render( ); const label = screen.getByText( props.label ); const colorButton = screen.getByLabelText( toggleLabelRegex ); - const widthInput = screen.getByRole( 'spinbutton' ); - const unitSelect = screen.getByRole( 'combobox' ); - const slider = screen.getByRole( 'slider' ); + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + const unitSelect = screen.getByRole( 'combobox', { + name: 'Select unit', + } ); + const slider = screen.getByRole( 'slider', { + name: 'Border width', + } ); const linkedButton = screen.getByLabelText( 'Unlink sides' ); expect( label ).toBeInTheDocument(); @@ -94,7 +77,8 @@ describe( 'BorderBoxControl', () => { } ); it( 'should hide label', () => { - renderBorderBoxControl( { hideLabelFromVision: true } ); + render( ); + const label = screen.getByText( props.label ); // As visually hidden labels are still included in the document @@ -107,62 +91,112 @@ describe( 'BorderBoxControl', () => { } ); it( 'should show correct width value when flat border value provided', () => { - renderBorderBoxControl( { value: defaultBorder } ); - const widthInput = screen.getByRole( 'spinbutton' ); + render( ); + + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); expect( widthInput.value ).toBe( '1' ); } ); it( 'should show correct width value when consistent split borders provided', () => { - renderBorderBoxControl( { value: defaultBorders } ); - const widthInput = screen.getByRole( 'spinbutton' ); + render( + + ); + + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); expect( widthInput.value ).toBe( '1' ); } ); - it( 'should render placeholder and omit unit select when border values are mixed', () => { - renderBorderBoxControl( { value: mixedBorders } ); + it( 'should render placeholder and omit unit select when border values are mixed', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); // First render of control with mixed values should show split view. - clickButton( 'Link sides' ); + await user.click( + screen.getByRole( 'button', { name: 'Link sides' } ) + ); - const widthInput = screen.getByRole( 'spinbutton' ); - const unitSelect = screen.queryByRole( 'combobox' ); + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + const unitSelect = screen.queryByRole( 'combobox', { + name: 'Select unit', + } ); expect( widthInput ).toHaveAttribute( 'placeholder', 'Mixed' ); expect( unitSelect ).not.toBeInTheDocument(); } ); it( 'should render shared border width and unit select when switching to linked view', async () => { - // Render control with mixed border values but consistent widths. - renderBorderBoxControl( { - value: { - top: { color: 'red', width: '5px', style: 'solid' }, - right: { color: 'blue', width: '5px', style: 'dashed' }, - bottom: { color: 'green', width: '5px', style: 'solid' }, - left: { color: 'yellow', width: '5px', style: 'dotted' }, - }, + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); + // Render control with mixed border values but consistent widths. + render( + + ); + // First render of control with mixed values should show split view. - clickButton( 'Link sides' ); - const linkedInput = screen.getByRole( 'spinbutton' ); - const unitSelect = screen.getByRole( 'combobox' ); + await user.click( + screen.getByRole( 'button', { name: 'Link sides' } ) + ); + + const linkedInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + const unitSelect = screen.getByRole( 'combobox', { + name: 'Select unit', + } ); expect( linkedInput.value ).toBe( '5' ); expect( unitSelect ).toBeInTheDocument(); } ); - it( 'should omit style options when requested', () => { - renderBorderBoxControl( { enableStyle: false } ); + it( 'should omit style options when requested', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); const colorButton = screen.getByLabelText( colorPickerRegex ); - fireEvent.click( colorButton ); + await user.click( colorButton ); const styleLabel = screen.queryByText( 'Style' ); - const solidButton = queryButton( 'Solid' ); - const dashedButton = queryButton( 'Dashed' ); - const dottedButton = queryButton( 'Dotted' ); + const solidButton = screen.queryByRole( 'button', { + name: 'Solid', + } ); + const dashedButton = screen.queryByRole( 'button', { + name: 'Dashed', + } ); + const dottedButton = screen.queryByRole( 'button', { + name: 'Dotted', + } ); expect( styleLabel ).not.toBeInTheDocument(); expect( solidButton ).not.toBeInTheDocument(); @@ -173,12 +207,18 @@ describe( 'BorderBoxControl', () => { describe( 'Split view rendering', () => { it( 'should render split view by default when mixed values provided', () => { - renderBorderBoxControl( { value: mixedBorders } ); + render( ); const colorButtons = screen.getAllByLabelText( toggleLabelRegex ); - const widthInputs = screen.getAllByRole( 'spinbutton' ); - const unitSelects = screen.getAllByRole( 'combobox' ); - const sliders = screen.queryAllByRole( 'slider' ); + const widthInputs = screen.getAllByRole( 'spinbutton', { + name: 'Border width', + } ); + const unitSelects = screen.getAllByRole( 'combobox', { + name: 'Select unit', + } ); + const sliders = screen.queryAllByRole( 'slider', { + name: 'Border width', + } ); const linkedButton = screen.getByLabelText( 'Link sides' ); expect( colorButtons.length ).toBe( 4 ); @@ -189,9 +229,11 @@ describe( 'BorderBoxControl', () => { } ); it( 'should render correct width values in appropriate inputs', () => { - renderBorderBoxControl( { value: mixedBorders } ); + render( ); - const widthInputs = screen.getAllByRole( 'spinbutton' ); + const widthInputs = screen.getAllByRole( 'spinbutton', { + name: 'Border width', + } ); expect( widthInputs[ 0 ].value ).toBe( '1' ); // Top. expect( widthInputs[ 1 ].value ).toBe( '0.75' ); // Left. @@ -199,39 +241,75 @@ describe( 'BorderBoxControl', () => { expect( widthInputs[ 3 ].value ).toBe( '2' ); // Bottom. } ); - it( 'should render split view correctly when starting with flat border', () => { - renderBorderBoxControl( { value: defaultBorders } ); - clickButton( 'Unlink sides' ); + it( 'should render split view correctly when starting with flat border', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + await user.click( + screen.getByRole( 'button', { name: 'Unlink sides' } ) + ); - const widthInputs = screen.getAllByRole( 'spinbutton' ); + const widthInputs = screen.getAllByRole( 'spinbutton', { + name: 'Border width', + } ); expect( widthInputs[ 0 ].value ).toBe( '1' ); // Top. expect( widthInputs[ 1 ].value ).toBe( '1' ); // Left. expect( widthInputs[ 2 ].value ).toBe( '1' ); // Right. expect( widthInputs[ 3 ].value ).toBe( '1' ); // Bottom. } ); - it( 'should omit style options when requested', () => { - renderBorderBoxControl( { enableStyle: false } ); - clickButton( 'Unlink sides' ); + it( 'should omit style options when requested', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); - const colorButtons = screen.getAllByLabelText( colorPickerRegex ); + render( ); + + await user.click( + screen.getByRole( 'button', { name: 'Unlink sides' } ) + ); - colorButtons.forEach( ( button ) => { - fireEvent.click( button ); + const colorButtons = screen.getAllByLabelText( colorPickerRegex ); + function assertStyleOptionsMissing() { const styleLabel = screen.queryByText( 'Style' ); - const solidButton = queryButton( 'Solid' ); - const dashedButton = queryButton( 'Dashed' ); - const dottedButton = queryButton( 'Dotted' ); + const solidButton = screen.queryByRole( 'button', { + name: 'Solid', + } ); + const dashedButton = screen.queryByRole( 'button', { + name: 'Dashed', + } ); + const dottedButton = screen.queryByRole( 'button', { + name: 'Dotted', + } ); expect( styleLabel ).not.toBeInTheDocument(); expect( solidButton ).not.toBeInTheDocument(); expect( dashedButton ).not.toBeInTheDocument(); expect( dottedButton ).not.toBeInTheDocument(); + } - fireEvent.click( button ); - } ); - } ); + await user.click( colorButtons[ 0 ] ); + assertStyleOptionsMissing(); + await user.click( colorButtons[ 0 ] ); + + await user.click( colorButtons[ 1 ] ); + assertStyleOptionsMissing(); + await user.click( colorButtons[ 1 ] ); + + await user.click( colorButtons[ 2 ] ); + assertStyleOptionsMissing(); + await user.click( colorButtons[ 2 ] ); + + await user.click( colorButtons[ 3 ] ); + assertStyleOptionsMissing(); + await user.click( colorButtons[ 3 ] ); + }, 10000 ); } ); describe( 'onChange handling', () => { @@ -241,16 +319,36 @@ describe( 'BorderBoxControl', () => { } ); describe( 'Linked value change handling', () => { - it( 'should set undefined when new border is empty', () => { - renderBorderBoxControl( { value: { width: '1px' } } ); - updateLinkedWidthInput( '' ); + it( 'should set undefined when new border is empty', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + await user.clear( + screen.getByRole( 'spinbutton', { name: 'Border width' } ) + ); expect( props.onChange ).toHaveBeenCalledWith( undefined ); } ); - it( 'should update with complete flat border', () => { - renderBorderBoxControl( { value: defaultBorder } ); - updateLinkedWidthInput( '3' ); + it( 'should update with complete flat border', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + await user.clear( widthInput ); + await user.type( widthInput, '3' ); expect( props.onChange ).toHaveBeenCalledWith( { ...defaultBorder, @@ -258,18 +356,32 @@ describe( 'BorderBoxControl', () => { } ); } ); - it( 'should maintain mixed values if not explicitly set via linked control', () => { - renderBorderBoxControl( { - value: { - top: { color: '#72aee6' }, - right: { color: '#f6f7f7', style: 'dashed' }, - bottom: { color: '#e65054', style: 'dotted' }, - left: { color: undefined }, - }, + it( 'should maintain mixed values if not explicitly set via linked control', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); - clickButton( 'Link sides' ); - updateLinkedWidthInput( '4' ); + render( + + ); + + await user.click( + screen.getByRole( 'button', { name: 'Link sides' } ) + ); + + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + await user.clear( widthInput ); + await user.type( widthInput, '4' ); expect( props.onChange ).toHaveBeenCalledWith( { top: { color: '#72aee6', width: '4px' }, @@ -279,9 +391,20 @@ describe( 'BorderBoxControl', () => { } ); } ); - it( 'should update with consistent split borders', () => { - renderBorderBoxControl( { value: defaultBorders } ); - updateLinkedWidthInput( '10' ); + it( 'should update with consistent split borders', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + await user.clear( widthInput ); + await user.type( widthInput, '10' ); expect( props.onChange ).toHaveBeenCalledWith( { ...defaultBorder, @@ -289,32 +412,56 @@ describe( 'BorderBoxControl', () => { } ); } ); - it( 'should set undefined borders when change results in empty borders', () => { - renderBorderBoxControl( { - value: { - top: { width: '1px' }, - right: { width: '1px' }, - bottom: { width: '1px' }, - left: { width: '1px' }, - }, + it( 'should set undefined borders when change results in empty borders', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); - updateLinkedWidthInput( '' ); + + render( + + ); + + await user.clear( + screen.getByRole( 'spinbutton', { name: 'Border width' } ) + ); expect( props.onChange ).toHaveBeenCalledWith( undefined ); } ); - it( 'should set flat border when change results in consistent split borders', () => { - renderBorderBoxControl( { - value: { - top: { ...defaultBorder, width: '1px' }, - right: { ...defaultBorder, width: '2px' }, - bottom: { ...defaultBorder, width: '3px' }, - left: { ...defaultBorder, width: '4px' }, - }, + it( 'should set flat border when change results in consistent split borders', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); - clickButton( 'Link sides' ); - updateLinkedWidthInput( '10' ); + render( + + ); + + await user.click( + screen.getByRole( 'button', { name: 'Link sides' } ) + ); + + const widthInput = screen.getByRole( 'spinbutton', { + name: 'Border width', + } ); + await user.clear( widthInput ); + await user.type( widthInput, '10' ); expect( props.onChange ).toHaveBeenCalledWith( { ...defaultBorder, @@ -324,7 +471,11 @@ describe( 'BorderBoxControl', () => { } ); describe( 'Split value change handling', () => { - it( 'should set split borders when the updated borders are mixed', () => { + it( 'should set split borders when the updated borders are mixed', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const borders = { top: { ...defaultBorder, width: '1px' }, right: { ...defaultBorder, width: '2px' }, @@ -332,8 +483,13 @@ describe( 'BorderBoxControl', () => { left: { ...defaultBorder, width: '4px' }, }; - renderBorderBoxControl( { value: borders } ); - updateSplitWidthInput( '5' ); + render( ); + + const widthInput = screen.getAllByRole( 'spinbutton', { + name: 'Border width', + } )[ 0 ]; + await user.clear( widthInput ); + await user.type( widthInput, '5' ); expect( props.onChange ).toHaveBeenCalledWith( { ...borders, @@ -341,7 +497,11 @@ describe( 'BorderBoxControl', () => { } ); } ); - it( 'should set flat border when updated borders are consistent', () => { + it( 'should set flat border when updated borders are consistent', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const borders = { top: { ...defaultBorder, width: '4px' }, right: { ...defaultBorder, width: '1px' }, @@ -349,8 +509,13 @@ describe( 'BorderBoxControl', () => { left: { ...defaultBorder, width: '1px' }, }; - renderBorderBoxControl( { value: borders } ); - updateSplitWidthInput( '1' ); + render( ); + + const widthInput = screen.getAllByRole( 'spinbutton', { + name: 'Border width', + } )[ 0 ]; + await user.clear( widthInput ); + await user.type( widthInput, '1' ); expect( props.onChange ).toHaveBeenCalledWith( defaultBorder ); } ); diff --git a/packages/components/src/border-box-control/types.ts b/packages/components/src/border-box-control/types.ts index d852a5bd1e40a..8e88e0b958c56 100644 --- a/packages/components/src/border-box-control/types.ts +++ b/packages/components/src/border-box-control/types.ts @@ -44,12 +44,11 @@ export type BorderBoxControlProps = ColorProps & */ value: AnyBorder; /** - * Start opting into the larger default height that will become the - * default size in a future version. + * Size of the control. * - * @default false + * @default 'default' */ - __next36pxDefaultSize?: boolean; + size?: 'default' | '__unstable-large'; }; export type LinkedButtonProps = { @@ -66,12 +65,11 @@ export type LinkedButtonProps = { */ onClick: () => void; /** - * Start opting into the larger default height that will become the - * default size in a future version. + * Size of the control. * - * @default false + * @default 'default' */ - __next36pxDefaultSize?: boolean; + size?: 'default' | '__unstable-large'; }; export type VisualizerProps = { @@ -82,12 +80,11 @@ export type VisualizerProps = { */ value?: Borders; /** - * Start opting into the larger default height that will become the - * default size in a future version. + * Size of the control. * - * @default false + * @default 'default' */ - __next36pxDefaultSize?: boolean; + size?: 'default' | '__unstable-large'; }; export type SplitControlsProps = ColorProps & { @@ -116,10 +113,9 @@ export type SplitControlsProps = ColorProps & { */ value?: Borders; /** - * Start opting into the larger default height that will become the - * default size in a future version. + * Size of the control. * - * @default false + * @default 'default' */ - __next36pxDefaultSize?: boolean; + size?: 'default' | '__unstable-large'; }; diff --git a/packages/components/src/border-control/border-control-dropdown/hook.ts b/packages/components/src/border-control/border-control-dropdown/hook.ts index bcf90b71a59db..a50ad72150054 100644 --- a/packages/components/src/border-control/border-control-dropdown/hook.ts +++ b/packages/components/src/border-control/border-control-dropdown/hook.ts @@ -22,7 +22,7 @@ export function useBorderControlDropdown( colors, onChange, previousStyleSelection, - __next36pxDefaultSize, + size = 'default', ...otherProps } = useContextSystem( props, 'BorderControlDropdown' ); @@ -53,18 +53,16 @@ export function useBorderControlDropdown( // Generate class names. const cx = useCx(); const classes = useMemo( () => { - return cx( styles.borderControlDropdown(), className ); - }, [ className, cx ] ); + return cx( styles.borderControlDropdown( size ), className ); + }, [ className, cx, size ] ); const indicatorClassName = useMemo( () => { return cx( styles.borderColorIndicator ); }, [ cx ] ); const indicatorWrapperClassName = useMemo( () => { - return cx( - styles.colorIndicatorWrapper( border, __next36pxDefaultSize ) - ); - }, [ border, cx, __next36pxDefaultSize ] ); + return cx( styles.colorIndicatorWrapper( border, size ) ); + }, [ border, cx, size ] ); const popoverControlsClassName = useMemo( () => { return cx( styles.borderControlPopoverControls ); diff --git a/packages/components/src/border-control/border-control/README.md b/packages/components/src/border-control/border-control/README.md index 718ec3d703908..83bf939b97961 100644 --- a/packages/components/src/border-control/border-control/README.md +++ b/packages/components/src/border-control/border-control/README.md @@ -135,6 +135,14 @@ dropdown. The header includes a label for the color picker and a close button. - Required: No +### `size`: `string` + +Size of the control. + +- Required: No +- Default: `default` +- Allowed values: `default`, `__unstable-large` + ### `value`: `Object` An object representing a border or `undefined`. Used to set the current border diff --git a/packages/components/src/border-control/border-control/component.tsx b/packages/components/src/border-control/border-control/component.tsx index cae27c1eefa4b..bf0beb8508546 100644 --- a/packages/components/src/border-control/border-control/component.tsx +++ b/packages/components/src/border-control/border-control/component.tsx @@ -53,6 +53,7 @@ const UnconnectedBorderControl = ( __unstablePopoverProps, previousStyleSelection, showDropdownHeader, + size = 'default', sliderClassName, value: border, widthUnit, @@ -60,7 +61,6 @@ const UnconnectedBorderControl = ( withSlider, __experimentalHasMultipleOrigins, __experimentalIsRenderedInSidebar, - __next36pxDefaultSize, ...otherProps } = useBorderControl( props ); @@ -70,7 +70,7 @@ const UnconnectedBorderControl = ( label={ label } hideLabelFromVision={ hideLabelFromVision } /> - + } label={ __( 'Border width' ) } @@ -100,6 +100,7 @@ const UnconnectedBorderControl = ( placeholder={ placeholder } disableUnits={ disableUnits } __unstableInputWidth={ inputWidth } + size={ size } /> { withSlider && ( { const widthStyle = !! wrapperWidth && styles.wrapperWidth; - const heightStyle = styles.wrapperHeight( __next36pxDefaultSize ); + const heightStyle = styles.wrapperHeight( size ); return cx( styles.innerWrapper(), widthStyle, heightStyle ); - }, [ wrapperWidth, cx, __next36pxDefaultSize ] ); + }, [ wrapperWidth, cx, size ] ); const sliderClassName = useMemo( () => { return cx( styles.borderSlider() ); @@ -143,6 +147,6 @@ export function useBorderControl( value: border, widthUnit, widthValue, - __next36pxDefaultSize, + size, }; } diff --git a/packages/components/src/border-control/stories/index.tsx b/packages/components/src/border-control/stories/index.tsx index 59aa440591b7f..2735e3ea2717b 100644 --- a/packages/components/src/border-control/stories/index.tsx +++ b/packages/components/src/border-control/stories/index.tsx @@ -36,12 +36,12 @@ export default meta; // Available border colors. const colors = [ - { name: 'Blue', color: '#72aee6' }, - { name: 'Red', color: '#e65054' }, - { name: 'Yellow', color: '#f2d675' }, - { name: 'Blue', color: '#72aee6' }, - { name: 'Red', color: '#e65054' }, - { name: 'Yellow', color: '#f2d675' }, + { name: 'Blue 20', color: '#72aee6' }, + { name: 'Blue 40', color: '#3582c4' }, + { name: 'Red 40', color: '#e65054' }, + { name: 'Red 70', color: '#8a2424' }, + { name: 'Yellow 10', color: '#f2d675' }, + { name: 'Yellow 40', color: '#bd8600' }, ]; // Multiple origin colors. @@ -84,14 +84,12 @@ const Template: ComponentStory< typeof BorderControl > = ( { return ( -
    - -
    - { /* @ts-expect-error Ignore until Popover is converted to TS */ } + + { /* @ts-expect-error Ignore until Popover.Slot is converted to TS */ }
    ); diff --git a/packages/components/src/border-control/styles.ts b/packages/components/src/border-control/styles.ts index 3c49e53a350ee..b105e8b62bd7b 100644 --- a/packages/components/src/border-control/styles.ts +++ b/packages/components/src/border-control/styles.ts @@ -13,7 +13,7 @@ import { StyledLabel, } from '../base-control/styles/base-control-styles'; import { - Root as UnitControlWrapper, + ValueInput as UnitControlWrapper, UnitSelect, } from '../unit-control/styles/unit-control-styles'; @@ -56,26 +56,28 @@ export const wrapperWidth = css` } `; -/* - * When default control height is 36px the following should be removed. - * See: InputControl and __next36pxDefaultSize. - */ -export const wrapperHeight = ( __next36pxDefaultSize?: boolean ) => { +export const wrapperHeight = ( size?: 'default' | '__unstable-large' ) => { return css` - height: ${ __next36pxDefaultSize ? '36px' : '30px' }; + height: ${ size === '__unstable-large' ? '40px' : '30px' }; `; }; -export const borderControlDropdown = () => css` +export const borderControlDropdown = ( + size?: 'default' | '__unstable-large' +) => css` background: #fff; && > button { /* - * Override button component height and padding to fit within - * BorderControl regardless of size. + * Override button component styles to fit within BorderControl + * regardless of size. */ - height: 100%; - padding: ${ space( 0.75 ) }; + height: ${ size === '__unstable-large' ? '40px' : '30px' }; + width: ${ size === '__unstable-large' ? '40px' : '30px' }; + padding: 0; + display: flex; + align-items: center; + justify-content: center; ${ rtl( { borderRadius: `2px 0 0 2px` }, { borderRadius: `0 2px 2px 0` } @@ -106,7 +108,7 @@ export const colorIndicatorBorder = ( border?: Border ) => { export const colorIndicatorWrapper = ( border?: Border, - __next36pxDefaultSize?: boolean + size?: 'default' | '__unstable-large' ) => { const { style } = border || {}; @@ -114,9 +116,9 @@ export const colorIndicatorWrapper = ( border-radius: 9999px; border: 2px solid transparent; ${ style ? colorIndicatorBorder( border ) : undefined } - width: ${ __next36pxDefaultSize ? '28px' : '22px' }; - height: ${ __next36pxDefaultSize ? '28px' : '22px' }; - padding: ${ __next36pxDefaultSize ? '2px' : '1px' }; + width: ${ size === '__unstable-large' ? '24px' : '22px' }; + height: ${ size === '__unstable-large' ? '24px' : '22px' }; + padding: ${ size === '__unstable-large' ? '2px' : '1px' }; /* * ColorIndicator @@ -125,13 +127,8 @@ export const colorIndicatorWrapper = ( * over the active state of the border control dropdown's toggle button. */ & > span { - ${ ! __next36pxDefaultSize - ? css` - /* Dimensions fit in 30px overall control height. */ - height: 16px; - width: 16px; - ` - : '' } + height: ${ space( 4 ) }; + width: ${ space( 4 ) }; background: linear-gradient( -45deg, transparent 48%, diff --git a/packages/components/src/border-control/test/index.js b/packages/components/src/border-control/test/index.js index 5ba468b35302d..922abba8f9c83 100644 --- a/packages/components/src/border-control/test/index.js +++ b/packages/components/src/border-control/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -22,28 +22,44 @@ const defaultBorder = { width: '1px', }; -const props = { - colors, - label: 'Border', - onChange: jest.fn().mockImplementation( ( newValue ) => { - props.value = newValue; - } ), - value: defaultBorder, -}; +function createProps( customProps ) { + const props = { + colors, + label: 'Border', + onChange: jest.fn().mockImplementation( ( newValue ) => { + props.value = newValue; + } ), + value: defaultBorder, + ...customProps, + }; + return props; +} const toggleLabelRegex = /Border color( and style)* picker/; -const renderBorderControl = ( customProps ) => { - return render( ); +const renderBorderControl = async ( props ) => { + const view = render( ); + // When the `Popover` component is rendered or updated, the `useFloating` + // hook from the `floating-ui` package will schedule a state update in a + // promise handler. We need to wait for this promise handler to execute + // before checking results. That's what this async `act()` call achieves. + // See also: https://floating-ui.com/docs/react-dom#testing + await act( () => Promise.resolve() ); + return view; }; -const rerenderBorderControl = ( rerender, customProps ) => { - return rerender( ); +const rerenderBorderControl = async ( rerender, props ) => { + const view = rerender( ); + // Same reason to `act()` as in `renderBorderControl` above. + await act( () => Promise.resolve() ); + return view; }; -const openPopover = () => { +const openPopover = async () => { const toggleButton = screen.getByLabelText( toggleLabelRegex ); fireEvent.click( toggleButton ); + // Same reason to `act()` as in `renderBorderControl` above. + await act( () => Promise.resolve() ); }; const getButton = ( name ) => { @@ -67,7 +83,9 @@ const getWidthInput = () => { }; const setWidthInput = ( value ) => { const widthInput = getWidthInput(); - widthInput.focus(); + act( () => { + widthInput.focus(); + } ); fireEvent.change( widthInput, { target: { value } } ); }; @@ -75,8 +93,9 @@ const clearWidthInput = () => setWidthInput( '' ); describe( 'BorderControl', () => { describe( 'basic rendering', () => { - it( 'should render standard border control', () => { - renderBorderControl(); + it( 'should render standard border control', async () => { + const props = createProps(); + await renderBorderControl( props ); const label = screen.getByText( props.label ); const colorButton = screen.getByLabelText( toggleLabelRegex ); @@ -95,8 +114,9 @@ describe( 'BorderControl', () => { expect( slider ).not.toBeInTheDocument(); } ); - it( 'should hide label', () => { - renderBorderControl( { hideLabelFromVision: true } ); + it( 'should hide label', async () => { + const props = createProps( { hideLabelFromVision: true } ); + await renderBorderControl( props ); const label = screen.getByText( props.label ); // As visually hidden labels are still included in the document @@ -108,23 +128,26 @@ describe( 'BorderControl', () => { ); } ); - it( 'should render with slider', () => { - renderBorderControl( { withSlider: true } ); + it( 'should render with slider', async () => { + const props = createProps( { withSlider: true } ); + await renderBorderControl( props ); const slider = getSliderInput(); expect( slider ).toBeInTheDocument(); } ); - it( 'should render placeholder in UnitControl', () => { - renderBorderControl( { placeholder: 'Mixed' } ); - const widthInput = getWidthInput(); + it( 'should render placeholder in UnitControl', async () => { + const props = createProps( { placeholder: 'Mixed' } ); + await renderBorderControl( props ); + const widthInput = getWidthInput(); expect( widthInput ).toHaveAttribute( 'placeholder', 'Mixed' ); } ); - it( 'should render color and style popover', () => { - renderBorderControl(); - openPopover(); + it( 'should render color and style popover', async () => { + const props = createProps(); + await renderBorderControl( props ); + await openPopover(); const customColorPicker = getButton( /Custom color picker/ ); const colorSwatchButtons = screen.getAllByRole( 'button', { @@ -145,9 +168,10 @@ describe( 'BorderControl', () => { expect( resetButton ).toBeInTheDocument(); } ); - it( 'should render color and style popover header', () => { - renderBorderControl( { showDropdownHeader: true } ); - openPopover(); + it( 'should render color and style popover header', async () => { + const props = createProps( { showDropdownHeader: true } ); + await renderBorderControl( props ); + await openPopover(); const headerLabel = screen.getByText( 'Border color' ); const closeButton = getButton( 'Close border color' ); @@ -156,9 +180,10 @@ describe( 'BorderControl', () => { expect( closeButton ).toBeInTheDocument(); } ); - it( 'should not render style options when opted out of', () => { - renderBorderControl( { enableStyle: false } ); - openPopover(); + it( 'should not render style options when opted out of', async () => { + const props = createProps( { enableStyle: false } ); + await renderBorderControl( props ); + await openPopover(); const styleLabel = screen.queryByText( 'Style' ); const solidButton = queryButton( 'Solid' ); @@ -174,16 +199,18 @@ describe( 'BorderControl', () => { describe( 'color and style picker aria labels', () => { describe( 'with style selection enabled', () => { - it( 'should include both color and style in label', () => { - renderBorderControl( { value: undefined } ); + it( 'should include both color and style in label', async () => { + const props = createProps( { value: undefined } ); + await renderBorderControl( props ); expect( screen.getByLabelText( 'Border color and style picker.' ) ).toBeInTheDocument(); } ); - it( 'should correctly describe named color selection', () => { - renderBorderControl( { value: { color: '#72aee6' } } ); + it( 'should correctly describe named color selection', async () => { + const props = createProps( { value: { color: '#72aee6' } } ); + await renderBorderControl( props ); expect( screen.getByLabelText( @@ -192,8 +219,9 @@ describe( 'BorderControl', () => { ).toBeInTheDocument(); } ); - it( 'should correctly describe custom color selection', () => { - renderBorderControl( { value: { color: '#4b1d80' } } ); + it( 'should correctly describe custom color selection', async () => { + const props = createProps( { value: { color: '#4b1d80' } } ); + await renderBorderControl( props ); expect( screen.getByLabelText( @@ -202,10 +230,11 @@ describe( 'BorderControl', () => { ).toBeInTheDocument(); } ); - it( 'should correctly describe named color and style selections', () => { - renderBorderControl( { + it( 'should correctly describe named color and style selections', async () => { + const props = createProps( { value: { color: '#72aee6', style: 'dotted' }, } ); + await renderBorderControl( props ); expect( screen.getByLabelText( @@ -214,10 +243,11 @@ describe( 'BorderControl', () => { ).toBeInTheDocument(); } ); - it( 'should correctly describe custom color and style selections', () => { - renderBorderControl( { + it( 'should correctly describe custom color and style selections', async () => { + const props = createProps( { value: { color: '#4b1d80', style: 'dashed' }, } ); + await renderBorderControl( props ); expect( screen.getByLabelText( @@ -228,19 +258,24 @@ describe( 'BorderControl', () => { } ); describe( 'with style selection disabled', () => { - it( 'should only include color in the label', () => { - renderBorderControl( { value: undefined, enableStyle: false } ); + it( 'should only include color in the label', async () => { + const props = createProps( { + value: undefined, + enableStyle: false, + } ); + await renderBorderControl( props ); expect( screen.getByLabelText( 'Border color picker.' ) ).toBeInTheDocument(); } ); - it( 'should correctly describe named color selection', () => { - renderBorderControl( { + it( 'should correctly describe named color selection', async () => { + const props = createProps( { value: { color: '#72aee6' }, enableStyle: false, } ); + await renderBorderControl( props ); expect( screen.getByLabelText( @@ -249,11 +284,12 @@ describe( 'BorderControl', () => { ).toBeInTheDocument(); } ); - it( 'should correctly describe custom color selection', () => { - renderBorderControl( { + it( 'should correctly describe custom color selection', async () => { + const props = createProps( { value: { color: '#4b1d80' }, enableStyle: false, } ); + await renderBorderControl( props ); expect( screen.getByLabelText( @@ -265,13 +301,9 @@ describe( 'BorderControl', () => { } ); describe( 'onChange handling', () => { - beforeEach( () => { - jest.clearAllMocks(); - props.value = defaultBorder; - } ); - - it( 'should update width with slider value', () => { - const { rerender } = renderBorderControl( { withSlider: true } ); + it( 'should update width with slider value', async () => { + const props = createProps( { withSlider: true } ); + const { rerender } = await renderBorderControl( props ); const slider = getSliderInput(); fireEvent.change( slider, { target: { value: '5' } } ); @@ -281,15 +313,16 @@ describe( 'BorderControl', () => { width: '5px', } ); - rerenderBorderControl( rerender, { withSlider: true } ); + await rerenderBorderControl( rerender, props ); const widthInput = getWidthInput(); expect( widthInput.value ).toEqual( '5' ); } ); - it( 'should update color selection', () => { - renderBorderControl(); - openPopover(); + it( 'should update color selection', async () => { + const props = createProps(); + await renderBorderControl( props ); + await openPopover(); clickButton( 'Color: Green' ); expect( props.onChange ).toHaveBeenNthCalledWith( 1, { @@ -298,9 +331,10 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should clear color selection when toggling swatch off', () => { - renderBorderControl(); - openPopover(); + it( 'should clear color selection when toggling swatch off', async () => { + const props = createProps(); + await renderBorderControl( props ); + await openPopover(); clickButton( 'Color: Blue' ); expect( props.onChange ).toHaveBeenNthCalledWith( 1, { @@ -309,9 +343,10 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should update style selection', () => { - renderBorderControl(); - openPopover(); + it( 'should update style selection', async () => { + const props = createProps(); + await renderBorderControl( props ); + await openPopover(); clickButton( 'Dashed' ); expect( props.onChange ).toHaveBeenNthCalledWith( 1, { @@ -320,17 +355,19 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should take no action when color and style popover is closed', () => { - renderBorderControl( { showDropdownHeader: true } ); - openPopover(); + it( 'should take no action when color and style popover is closed', async () => { + const props = createProps( { showDropdownHeader: true } ); + await renderBorderControl( props ); + await openPopover(); clickButton( 'Close border color' ); expect( props.onChange ).not.toHaveBeenCalled(); } ); - it( 'should reset color and style only when popover reset button clicked', () => { - renderBorderControl(); - openPopover(); + it( 'should reset color and style only when popover reset button clicked', async () => { + const props = createProps(); + await renderBorderControl( props ); + await openPopover(); clickButton( 'Reset to default' ); expect( props.onChange ).toHaveBeenNthCalledWith( 1, { @@ -340,23 +377,25 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should sanitize border when width and color are undefined', () => { - const { rerender } = renderBorderControl(); + it( 'should sanitize border when width and color are undefined', async () => { + const props = createProps(); + const { rerender } = await renderBorderControl( props ); clearWidthInput(); - rerenderBorderControl( rerender ); - openPopover(); + await rerenderBorderControl( rerender, props ); + await openPopover(); clickButton( 'Color: Blue' ); expect( props.onChange ).toHaveBeenCalledWith( undefined ); } ); - it( 'should not sanitize border when requested', () => { - const { rerender } = renderBorderControl( { + it( 'should not sanitize border when requested', async () => { + const props = createProps( { shouldSanitizeBorder: false, } ); + const { rerender } = await renderBorderControl( props ); clearWidthInput(); - rerenderBorderControl( rerender, { shouldSanitizeBorder: false } ); - openPopover(); + await rerenderBorderControl( rerender, props ); + await openPopover(); clickButton( 'Color: Blue' ); expect( props.onChange ).toHaveBeenNthCalledWith( 2, { @@ -366,9 +405,10 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should clear color and set style to `none` when setting zero width', () => { - renderBorderControl(); - openPopover(); + it( 'should clear color and set style to `none` when setting zero width', async () => { + const props = createProps(); + await renderBorderControl( props ); + await openPopover(); clickButton( 'Color: Green' ); clickButton( 'Dotted' ); setWidthInput( '0' ); @@ -380,13 +420,14 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should reselect color and style selections when changing to non-zero width', () => { - const { rerender } = renderBorderControl(); - openPopover(); + it( 'should reselect color and style selections when changing to non-zero width', async () => { + const props = createProps(); + const { rerender } = await renderBorderControl( props ); + await openPopover(); clickButton( 'Color: Green' ); - rerenderBorderControl( rerender ); + await rerenderBorderControl( rerender, props ); clickButton( 'Dotted' ); - rerenderBorderControl( rerender ); + await rerenderBorderControl( rerender, props ); setWidthInput( '0' ); setWidthInput( '5' ); @@ -397,9 +438,10 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should set a non-zero width when applying color to zero width border', () => { - const { rerender } = renderBorderControl( { value: undefined } ); - openPopover(); + it( 'should set a non-zero width when applying color to zero width border', async () => { + const props = createProps( { value: undefined } ); + const { rerender } = await renderBorderControl( props ); + await openPopover(); clickButton( 'Color: Yellow' ); expect( props.onChange ).toHaveBeenCalledWith( { @@ -409,7 +451,7 @@ describe( 'BorderControl', () => { } ); setWidthInput( '0' ); - rerenderBorderControl( rerender ); + await rerenderBorderControl( rerender, props ); clickButton( 'Color: Green' ); expect( props.onChange ).toHaveBeenCalledWith( { @@ -419,12 +461,13 @@ describe( 'BorderControl', () => { } ); } ); - it( 'should set a non-zero width when applying style to zero width border', () => { - const { rerender } = renderBorderControl( { + it( 'should set a non-zero width when applying style to zero width border', async () => { + const props = createProps( { value: undefined, shouldSanitizeBorder: false, } ); - openPopover(); + const { rerender } = await renderBorderControl( props ); + await openPopover(); clickButton( 'Dashed' ); expect( props.onChange ).toHaveBeenCalledWith( { @@ -434,7 +477,7 @@ describe( 'BorderControl', () => { } ); setWidthInput( '0' ); - rerenderBorderControl( rerender, { shouldSanitizeBorder: false } ); + await rerenderBorderControl( rerender, props ); clickButton( 'Dotted' ); expect( props.onChange ).toHaveBeenCalledWith( { diff --git a/packages/components/src/border-control/types.ts b/packages/components/src/border-control/types.ts index 3ff10dae8ed9b..7710fb4705493 100644 --- a/packages/components/src/border-control/types.ts +++ b/packages/components/src/border-control/types.ts @@ -16,7 +16,7 @@ export type Border = { export type Color = { name: string; - color: CSSProperties[ 'color' ]; + color: NonNullable< CSSProperties[ 'color' ] >; }; export type ColorOrigin = { @@ -123,12 +123,11 @@ export type BorderControlProps = ColorProps & */ withSlider?: boolean; /** - * Start opting into the larger default height that will become the - * default size in a future version. + * Size of the control. * - * @default false + * @default 'default' */ - __next36pxDefaultSize?: boolean; + size?: 'default' | '__unstable-large'; }; export type DropdownProps = ColorProps & { @@ -165,12 +164,11 @@ export type DropdownProps = ColorProps & { */ showDropdownHeader?: boolean; /** - * Start opting into the larger default height that will become the - * default size in a future version. + * Size of the control. * - * @default false + * @default 'default' */ - __next36pxDefaultSize?: boolean; + size?: 'default' | '__unstable-large'; }; export type StylePickerProps = LabelProps & { diff --git a/packages/components/src/box-control/README.md b/packages/components/src/box-control/README.md index 8a704482cacd1..edf3993679033 100644 --- a/packages/components/src/box-control/README.md +++ b/packages/components/src/box-control/README.md @@ -95,3 +95,17 @@ The `top`, `right`, `bottom`, and `left` box dimension values. - Type: `Object` - Required: No + +### onMouseOver + +A handler for onMouseOver events. + +- Type: `Function` +- Required: No + +### onMouseOut + +A handler for onMouseOut events. + +- Type: `Function` +- Required: No diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index f5e8e7428c801..d8a0fdf4c6748 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -52,6 +52,8 @@ export default function BoxControl( { splitOnAxis = false, allowReset = true, resetValues = DEFAULT_VALUES, + onMouseOver, + onMouseOut, } ) { const [ values, setValues ] = useControlledState( valuesProp, { fallback: DEFAULT_VALUES, @@ -114,6 +116,8 @@ export default function BoxControl( { setSelectedUnits, sides, values: inputValues, + onMouseOver, + onMouseOut, }; return ( diff --git a/packages/components/src/box-control/stories/index.js b/packages/components/src/box-control/stories/index.js index f0bab8e21481d..55cf2628c670c 100644 --- a/packages/components/src/box-control/stories/index.js +++ b/packages/components/src/box-control/stories/index.js @@ -1,17 +1,12 @@ -/** - * External dependencies - */ -import styled from '@emotion/styled'; - /** * WordPress dependencies */ import { useState } from '@wordpress/element'; + /** * Internal dependencies */ import BoxControl from '../'; -import { Flex, FlexBlock } from '../../flex'; export default { title: 'Components (Experimental)/BoxControl', @@ -37,19 +32,13 @@ function DemoExample( { const [ values, setValues ] = useState( defaultValues ); return ( - - - - - - - + ); } @@ -84,11 +73,3 @@ export const axialControlsWithSingleSide = () => { /> ); }; - -const Container = styled( Flex )` - max-width: 780px; -`; - -const Content = styled.div` - padding: 20px; -`; diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 3bf23aed95d47..b8b3a7f3111d2 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -127,7 +127,6 @@ Whether the button is disabled. If `true`, this will force a `button` element to - Type: `Boolean` - Required: No -- Default: `false` #### href @@ -135,7 +134,6 @@ If provided, renders `a` instead of `button`. - Type: `String` - Required: No -- Default: `undefined` #### variant @@ -143,7 +141,6 @@ Specifies the button's style. The accepted values are `'primary'` (the primary b - Type: `String` - Required: No -- Default: `undefined` #### isDestructive @@ -151,7 +148,6 @@ Renders a red text-based button style to indicate destructive behavior. - Type: `Boolean` - Required: No -- Default: `false` #### isSmall @@ -159,7 +155,6 @@ Decreases the size of the button. - Type: `Boolean` - Required: No -- Default: `false` #### isPressed @@ -167,7 +162,6 @@ Renders a pressed button style. - Type: `Boolean` - Required: No -- Default: `false` #### isBusy @@ -175,7 +169,6 @@ Indicates activity while a action is being performed. - Type: `Boolean` - Required: No -- Default: `false` #### focus @@ -183,7 +176,6 @@ Whether the button is focused. - Type: `Boolean` - Required: No -- Default: `false` #### target @@ -205,15 +197,13 @@ If provided, renders an [Icon](/packages/components/src/icon/README.md) componen - Type: `String|Function|WPComponent|null` - Required: No -- Default: `null` #### iconSize -If provided with `icon`, sets the icon size. +If provided with `icon`, sets the icon size. Please refer to the [Icon](/packages/components/src/icon/README.md) component for more details regarding the default value of its `size` prop. - Type: `Number` - Required: No -- Default: `20 when a Dashicon is rendered, 24 for all other icons.` #### iconPosition @@ -236,15 +226,13 @@ If provided, renders a [Tooltip](/packages/components/src/tooltip/README.md) com - Type: `Boolean` - Required: No -- Default: `false` #### tooltipPosition -If provided with`showTooltip`, sets the position of the tooltip. +If provided with`showTooltip`, sets the position of the tooltip. Please refer to the [Tooltip](/packages/components/src/tooltip/README.md) component for more details regarding the defaults. - Type: `String` - Require: No -- Default:`top center` #### shortcut @@ -252,7 +240,6 @@ If provided with `showTooltip`, appends the Shortcut label to the tooltip conten - Type: `String|Object` - Required: No -- Default: `undefined` #### label diff --git a/packages/components/src/button/stories/index.js b/packages/components/src/button/stories/index.js index f71823d073464..428d1851c6640 100644 --- a/packages/components/src/button/stories/index.js +++ b/packages/components/src/button/stories/index.js @@ -1,15 +1,10 @@ -/** - * External dependencies - */ -import { text, number, boolean } from '@storybook/addon-knobs'; - /** * WordPress dependencies */ import { formatBold, formatItalic, - link as linkIcon, + link, more, wordpress, } from '@wordpress/icons'; @@ -23,142 +18,120 @@ import Button from '../'; export default { title: 'Components/Button', component: Button, + argTypes: { + label: { + control: { type: 'text' }, + description: + 'Sets the `aria-label` of the component, if none is provided. Sets the Tooltip content if `showTooltip` is provided.', + }, + children: { + control: { type: 'text' }, + }, + href: { + control: { type: 'text' }, + description: 'If provided, renders `a` instead of `button`.', + }, + icon: { + control: { type: 'select' }, + description: + 'If provided, renders an `Icon` component inside the button.', + options: [ 'wordpress', 'link', 'more' ], + mapping: { + wordpress, + link, + more, + }, + }, + iconSize: { + control: { type: 'number' }, + description: 'If provided with `icon`, sets the icon size.', + }, + iconPosition: { + control: { type: 'select' }, + description: + 'If provided with `icon`, sets the position of icon relative to the `text`. Available options are `left|right`.', + options: [ 'left', 'right' ], + table: { + defaultValue: { summary: `left` }, + }, + }, + isBusy: { + control: 'boolean', + description: + 'Indicates activity while a action is being performed.', + }, + isDestructive: { + control: 'boolean', + description: + 'Renders a red text-based button style to indicate destructive behavior.', + }, + isPressed: { + control: 'boolean', + description: 'Renders a pressed button style.', + }, + isSmall: { + control: 'boolean', + description: 'Decreases the size of the button.', + }, + disabled: { + control: 'boolean', + description: + 'Whether the button is disabled. If `true`, this will force a `button` element to be rendered.', + }, + shortcut: { + control: { type: 'text' }, + description: + 'If provided with `showTooltip`, appends the Shortcut label to the tooltip content. If an `Object` is provided, it should contain `display` and `ariaLabel` keys.', + }, + showTooltip: { + control: 'boolean', + description: + 'If provided, renders a `Tooltip` component for the button.', + }, + tooltipPosition: { + control: { type: 'text' }, + description: + 'If provided with `showTooltip`, sets the position of the tooltip.', + }, + text: { + control: { type: 'text' }, + description: + 'If provided, displays the given text inside the button. If the button contains `children` elements, the text is displayed before them.', + }, + target: { + control: { type: 'text' }, + description: + 'If provided with `href`, sets the `target` attribute to the `a`.', + }, + variant: { + control: { type: 'select' }, + description: "Specifies the button's style.", + options: [ 'primary', 'secondary', 'tertiary', 'link' ], + }, + __experimentalIsFocusable: { + control: 'boolean', + description: 'Makes the button focusable even when disabled', + }, + }, parameters: { - knobs: { disable: false }, + controls: { expanded: true }, + docs: { source: { state: 'open' } }, }, }; -export const _default = () => { - const label = text( 'Label', 'Default Button' ); - - return ; -}; - -export const primary = () => { - const label = text( 'Label', 'Primary Button' ); - - return ; -}; - -export const secondary = () => { - const label = text( 'Label', 'Secondary Button' ); - - return ; -}; - -export const tertiary = () => { - const label = text( 'Label', 'Tertiary Button' ); - - return ; -}; - -export const isDestructive = () => { - const label = text( 'Label', 'Destructive Button' ); - const isSmall = boolean( 'isSmall', false ); - const disabled = boolean( 'disabled', false ); +function Template( { children, ...props } ) { + return ; +} - return ( - - ); -}; - -export const isPrimaryDestructive = () => { - const label = text( 'Label', 'Destructive Primary Button' ); - const isSmall = boolean( 'isSmall', false ); - const disabled = boolean( 'disabled', false ); - - return ( - - ); -}; - -export const small = () => { - const label = text( 'Label', 'Small Button' ); - - return ; -}; - -export const pressed = () => { - const label = text( 'Label', 'Pressed Button' ); - - return ; +export const Default = Template.bind( {} ); +Default.args = { + children: 'Code is poetry', }; -export const disabled = () => { - const label = text( 'Label', 'Disabled Button' ); - - return ; -}; - -export const disabledFocusable = () => { - const label = text( 'Label', 'Disabled Button' ); - - return ( - - ); -}; - -export const link = () => { - const label = text( 'Label', 'Link Button' ); - - return ( - - ); -}; - -export const disabledLink = () => { - const label = text( 'Label', 'Disabled Link Button' ); - - return ( - - ); -}; - -export const destructiveLink = () => { - const label = text( 'Label', 'Destructive Link' ); - - return ( - - ); -}; - -export const icon = () => { - const label = text( 'Label', 'Code is poetry' ); - const size = number( 'Size', 24 ); - - return - - - - -
  • - -

    Regular Buttons

    -
    - - - - - -
    - - ); -}; diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 78edbb6463f64..29b8d4aa2cba9 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -171,52 +171,30 @@ /** * Destructive buttons. */ - &.is-destructive { - color: $alert-red; - box-shadow: inset 0 0 0 $border-width $alert-red; - - &:hover:not(:disabled) { - color: darken($alert-red, 20%); - box-shadow: inset 0 0 0 $border-width darken($alert-red, 20%); - } - - &:focus:not(:disabled) { - color: $components-color-accent; - } - - &:active:not(:disabled) { - background: $gray-400; - } - } + --wp-components-color-accent: #{$alert-red}; + --wp-components-color-accent-darker-10: #{darken($alert-red, 10%)}; + --wp-components-color-accent-darker-20: #{darken($alert-red, 20%)}; - &.is-destructive.is-primary { - color: #fff; - background: $alert-red; - box-shadow: inset 0 0 0 $border-width $alert-red; - - &:hover:not(:disabled) { - color: #fff; - background: darken($alert-red, 20%); - box-shadow: inset 0 0 0 $border-width darken($alert-red, 20%); - } - } + // Only the default variant is styled differently from the non-destructive counterparts. + &:not(.is-primary):not(.is-secondary):not(.is-tertiary):not(.is-link) { + color: $alert-red; + box-shadow: inset 0 0 0 $border-width $alert-red; - &.is-destructive.is-tertiary { - box-shadow: none; + &:hover:not(:disabled) { + color: darken($alert-red, 20%); + } - &:hover:not(:disabled) { - box-shadow: inset 0 0 0 $border-width $alert-red; - color: $alert-red; - } + &:focus:not(:disabled) { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $alert-red; + } - &:focus:not(:disabled) { - box-shadow: inset 0 0 0 1px $white, 0 0 0 var(--wp-admin-border-width-focus) $alert-red; - color: $alert-red; + &:active:not(:disabled) { + background: $gray-400; + } } } - /** * Link buttons. */ @@ -241,21 +219,6 @@ &:focus { border-radius: $radius-block-ui; } - - // Link buttons that are red to indicate destructive behavior. - &.is-destructive { - color: $alert-red; - - &:active:not(:disabled), - &:hover:not(:disabled) { - color: darken($alert-red, 20%); - background: none; - } - - &:focus:not(:disabled) { - color: $components-color-accent; - } - } } &:not([aria-disabled="true"]):active { @@ -315,7 +278,7 @@ } &.has-text { - justify-content: left; + justify-content: start; } &.has-text svg { diff --git a/packages/components/src/card/stories/index.tsx b/packages/components/src/card/stories/index.tsx index c771dc62cdbfe..a6de3a458cf60 100644 --- a/packages/components/src/card/stories/index.tsx +++ b/packages/components/src/card/stories/index.tsx @@ -40,36 +40,32 @@ const meta: ComponentMeta< typeof Card > = { export default meta; -const Template: ComponentStory< typeof Card > = ( args ) => { - return ( -
    - - - CardHeader - - - CardBody - - - CardBody (before CardDivider) - - - - CardBody (after CardDivider) - - - Card Media - - - CardFooter - - - -
    - ); -}; +const Template: ComponentStory< typeof Card > = ( args ) => ( + + + CardHeader + + + CardBody + + + CardBody (before CardDivider) + + + + CardBody (after CardDivider) + + + Card Media + + + CardFooter + + + +); export const Default: ComponentStory< typeof Card > = Template.bind( {} ); diff --git a/packages/components/src/card/test/__snapshots__/index.tsx.snap b/packages/components/src/card/test/__snapshots__/index.tsx.snap index 2325e13746aa9..2abd66b0067ea 100644 --- a/packages/components/src/card/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/card/test/__snapshots__/index.tsx.snap @@ -90,27 +90,27 @@ Snapshot Diff: - First value + Second value -@@ -5,18 +5,18 @@ - > -
    - Header -
    -
    - Code is Poetry -
    +
    + Header +
    +
    + Code is Poetry +
    `; exports[`Card Card component should add rounded border when the isRounded prop is true 1`] = ` @@ -768,24 +768,25 @@ Snapshot Diff: - First value + Second value -@@ -8,16 +8,16 @@ - > - Code is Poetry +@@ -9,17 +9,17 @@ + > + Code is Poetry +
    +