diff --git a/.eslintrc.js b/.eslintrc.js index 9ac141fd09a04..0b0c71c39a266 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -76,6 +76,11 @@ const restrictedImports = [ message: "edit-widgets is a WordPress top level package that shouldn't be imported into other packages", }, + { + name: 'classnames', + message: + "Please use `clsx` instead. It's a lighter and faster drop-in replacement for `classnames`.", + }, ]; module.exports = { diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 2df5fd2a8725f..f6527c3de9d97 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -15,3 +15,6 @@ c56e8a1910ed74f405b74bbb12fe81dea974e5c3 # ESLint: Enable react/jsx-boolean-value 9a34927870df80ac3b2da14d71f81d20ec23e2b6 + +# Autofix eslint curly rule. +0221522f253e094b277a1485b7a2d186cb172632 diff --git a/.github/setup-node/action.yml b/.github/setup-node/action.yml index fccce2e4e93bc..a17adfe5f5007 100644 --- a/.github/setup-node/action.yml +++ b/.github/setup-node/action.yml @@ -10,7 +10,7 @@ runs: using: 'composite' steps: - name: Use desired version of Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version-file: '.nvmrc' node-version: ${{ inputs.node-version }} @@ -25,15 +25,22 @@ runs: - name: Cache node_modules id: cache-node_modules - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: '**/node_modules' - key: node_modules-${{ runner.os }}-${{ steps.node-version.outputs.NODE_VERSION }}-${{ hashFiles('package-lock.json') }} + key: node_modules-${{ runner.os }}-${{ runner.arch }}-${{ steps.node-version.outputs.NODE_VERSION }}-${{ hashFiles('package-lock.json') }} - name: Install npm dependencies if: ${{ steps.cache-node_modules.outputs.cache-hit != 'true' }} - run: npm ci + run: | + npm ci shell: bash + - name: Upload npm logs as an artifact on failure + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: failure() + with: + name: npm-logs + path: C:\npm\cache\_logs # On cache hit, we run the post-install script to match the native `npm ci` behavior. # An example of this is to patch `node_modules` using patch-package. diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 7facf92e00dd4..d1621ed5106aa 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -69,7 +69,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: token: ${{ secrets.GUTENBERG_TOKEN }} show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -165,7 +165,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ needs.bump-version.outputs.release_branch || github.ref }} show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -183,7 +183,7 @@ jobs: NO_CHECKS: 'true' - name: Upload artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: gutenberg-plugin path: ./gutenberg.zip @@ -206,7 +206,7 @@ jobs: - name: Upload release notes artifact if: ${{ needs.bump-version.outputs.new_version }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: release-notes path: ./release-notes.txt @@ -222,7 +222,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 2 ref: ${{ needs.bump-version.outputs.release_branch }} @@ -270,12 +270,12 @@ jobs: run: echo "version=$(echo $VERSION | cut -d / -f 3 | sed 's/-rc./ RC/' )" >> $GITHUB_OUTPUT - name: Download Plugin Zip Artifact - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: gutenberg-plugin - name: Download Release Notes Artifact - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: release-notes @@ -311,14 +311,14 @@ jobs: if: ${{ endsWith( needs.bump-version.outputs.new_version, '-rc.1' ) }} steps: - name: Checkout (for CLI) - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: main ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Checkout (for publishing) - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: publish # Later, we switch this branch in the script that publishes packages. diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index a1c66221a76af..8eafe4267bc43 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 1 show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -49,7 +49,7 @@ jobs: check-latest: true cache: npm - - uses: preactjs/compressed-size-action@8119d3d31b6e57b167e09c81dfa877eada3bcb35 # v2.5.0 + - uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c # v2.6.0 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' pattern: '{build/**/*.min.js,build/**/*.css}' diff --git a/.github/workflows/check-components-changelog.yml b/.github/workflows/check-components-changelog.yml index 8af0dc05f4108..77fdf4759f7de 100644 --- a/.github/workflows/check-components-changelog.yml +++ b/.github/workflows/check-components-changelog.yml @@ -20,7 +20,7 @@ jobs: - name: 'Get PR commit count' run: echo "PR_COMMIT_COUNT=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}" - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml index 5fec67935cdf0..2b93546926480 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -24,7 +24,7 @@ jobs: os: ['macos-latest', 'ubuntu-latest', 'windows-latest'] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 23545c70f512e..c919e733360ff 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -27,7 +27,7 @@ jobs: totalParts: [8] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -52,7 +52,7 @@ jobs: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e -- --shard=${{ matrix.part }}/${{ matrix.totalParts }} - name: Archive debug artifacts (screenshots, traces) - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: always() with: name: failures-artifacts @@ -60,7 +60,7 @@ jobs: if-no-files-found: ignore - name: Archive flaky tests report - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: always() with: name: flaky-tests-report @@ -75,12 +75,12 @@ jobs: steps: # Checkout defaults to using the branch which triggered the event, which # isn't necessarily `trunk` (e.g. in the case of a merge). - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: actions/download-artifact@v4.1.4 + - uses: actions/download-artifact@v4.1.7 id: download_artifact # Don't fail the job if there isn't any flaky tests report. continue-on-error: true diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 5fdda64ea010f..bcd0fee6453fd 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,7 +6,7 @@ jobs: name: 'Validation' runs-on: ubuntu-latest steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - uses: gradle/wrapper-validation-action@v3 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 143bf71ab2e3a..b78ff9532c22d 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -32,7 +32,7 @@ jobs: WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -68,13 +68,13 @@ jobs: - name: Compare performance with base branch if: github.event_name == 'push' # The base hash used here need to be a commit that is compatible with the current WP version - # The current one is 9bb75b35ec0daa46e8cd60c46619f522c7edd453 and it needs to be updated every WP major release. + # The current one is 9725060a5b18904c6cc5fdbe4b06fbde7419e02c and it needs to be updated every WP major release. # It is used as a base comparison point to avoid fluctuation in the performance metrics. run: | 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 $GITHUB_SHA 9bb75b35ec0daa46e8cd60c46619f522c7edd453 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf $GITHUB_SHA 9725060a5b18904c6cc5fdbe4b06fbde7419e02c --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - name: Compare performance with custom branches if: github.event_name == 'workflow_dispatch' @@ -86,7 +86,7 @@ jobs: - name: Archive performance results if: success() - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: performance-results path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json @@ -97,10 +97,10 @@ jobs: CODEHEALTH_PROJECT_TOKEN: ${{ secrets.CODEHEALTH_PROJECT_TOKEN }} run: | COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") - ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA 9bb75b35ec0daa46e8cd60c46619f522c7edd453 $COMMITTED_AT + ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA 9725060a5b18904c6cc5fdbe4b06fbde7419e02c $COMMITTED_AT - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: failure() with: name: failures-artifacts diff --git a/.github/workflows/php-changes-detection.yml b/.github/workflows/php-changes-detection.yml index f2529e873816a..ba34e0d806185 100644 --- a/.github/workflows/php-changes-detection.yml +++ b/.github/workflows/php-changes-detection.yml @@ -10,14 +10,14 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} steps: - name: Check out code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 0 show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Get changed PHP files id: changed-files-php - uses: tj-actions/changed-files@2d756ea4c53f7f6b397767d8723b3a10a9f35bf2 # v44.0.0 + uses: tj-actions/changed-files@0874344d6ebbaa00a27da73276ae7162fadcaf69 # v44.3.0 with: files: | **.{php} diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 038d8de8ed2e1..11dfdb878ef28 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout (for CLI) if: ${{ github.event.inputs.release_type != 'wp' }} - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: cli ref: trunk @@ -39,7 +39,7 @@ jobs: - name: Checkout (for publishing) if: ${{ github.event.inputs.release_type != 'wp' }} - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: publish # Later, we switch this branch in the script that publishes packages. @@ -49,7 +49,7 @@ jobs: - name: Checkout (for publishing WP major version) if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }} - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: publish ref: wp/${{ github.event.inputs.wp_version }} diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml index 73fc286684885..05d28f888d0ae 100644 --- a/.github/workflows/pull-request-automation.yml +++ b/.github/workflows/pull-request-automation.yml @@ -12,7 +12,7 @@ jobs: steps: # Checkout defaults to using the branch which triggered the event, which # isn't necessarily `trunk` (e.g. in the case of a merge). - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 9f4f60996e209..5d1d476226b12 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -23,7 +23,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -47,7 +47,7 @@ jobs: run: npm run native test:e2e:setup - name: Gradle cache - uses: gradle/actions/setup-gradle@6cec5d49d4d6d4bb982fbed7047db31ea6d38f11 # v3.3.0 + uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 - name: AVD cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 @@ -81,13 +81,13 @@ jobs: profile: Nexus 6 script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} - - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: always() with: name: android-screen-recordings path: packages/react-native-editor/android-screen-recordings - - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: always() with: name: appium-logs diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 0484faa9cdfe7..5056527d097bd 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -23,11 +23,11 @@ jobs: native-test-name: [gutenberg-editor-rendering] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 + - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0 with: # `.ruby-version` file location working-directory: packages/react-native-editor/ios @@ -89,13 +89,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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: always() with: name: ios-screen-recordings path: packages/react-native-editor/ios-screen-recordings - - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: always() with: name: appium-logs diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index fb8f33a103293..12c8931efca06 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -22,7 +22,7 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 1f79fb208094c..56b7471f06d9b 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -24,7 +24,7 @@ jobs: run: npm run storybook:build - name: Deploy - uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./storybook/build diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 551a77f8bed6d..22bca2dc78186 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -70,7 +70,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -121,7 +121,7 @@ jobs: name: Build JavaScript assets for PHP unit tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -132,7 +132,7 @@ jobs: run: npm run build - name: Upload built JavaScript assets - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: build-assets path: ./build/ @@ -157,7 +157,7 @@ jobs: wordpress: [''] # Latest WordPress version. include: # Test with the previous WP version. - - php: '7.0' + - php: '7.2' wordpress: ${{ needs.compute-previous-wordpress-version.outputs.previous-wordpress-version }} - php: '7.4' wordpress: ${{ needs.compute-previous-wordpress-version.outputs.previous-wordpress-version }} @@ -170,7 +170,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -185,7 +185,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@c665c7a15b5295c2488ac8a87af9cb806cd72198 # v2.30.4 with: php-version: '${{ matrix.php }}' ini-file: development @@ -209,7 +209,7 @@ jobs: custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - name: Download built JavaScript assets - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: build-assets path: ./build @@ -281,12 +281,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@c665c7a15b5295c2488ac8a87af9cb806cd72198 # v2.30.4 with: php-version: '7.4' coverage: none @@ -351,7 +351,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index 7ca7283f9cfde..8a92d0443d577 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -96,7 +96,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ matrix.branch }} token: ${{ secrets.GUTENBERG_TOKEN }} @@ -147,7 +147,7 @@ jobs: fi - name: Upload Changelog artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: changelog ${{ matrix.label }} path: ./changelog.txt @@ -189,7 +189,7 @@ jobs: sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" ./trunk/readme.txt - name: Download Changelog Artifact - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: changelog trunk path: trunk @@ -247,7 +247,7 @@ jobs: sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" "$VERSION/readme.txt" - name: Download Changelog Artifact - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: changelog trunk path: ${{ github.event.release.name }} diff --git a/README.md b/README.md index 33f7671647075..360b2851be092 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ Welcome to the development hub for the WordPress Gutenberg project! -"Gutenberg" is a codename for a whole new paradigm in WordPress site building and publishing, that aims to revolutionize the entire publishing experience as much as Gutenberg did the printed word. Right now, the project is in the second phase of a four-phase process that will touch every piece of WordPress -- Editing, **Customization** (which includes Full Site Editing, Block Patterns, Block Directory and Block based themes), Collaboration, and Multilingual -- and is focused on a new editing experience, the block editor. +"Gutenberg" is a codename for a whole new paradigm in WordPress site building and publishing, that aims to revolutionize the entire publishing experience as much as Gutenberg did the printed word. Right now, the project is in the second phase of a four-phase process that will touch every piece of WordPress -- Editing, Customization, **Collaboration** (which includes [Real-time collaboration](https://make.wordpress.org/core/2023/07/03/real-time-collaboration/), [Asynchronous collaboration](https://make.wordpress.org/core/2023/07/04/workflows/), [Publishing flows](https://make.wordpress.org/core/2023/07/04/workflows/), [Post revisions interface](https://make.wordpress.org/core/2023/07/05/revisions/), [Admin design](https://make.wordpress.org/core/2023/07/12/admin-design/), [Library](https://make.wordpress.org/core/2023/07/10/block-library/)), and Multilingual -- and is focused on a new editing experience, the block editor. The block editor introduces a modular approach to pages and posts: each piece of content in the editor, from a paragraph to an image gallery to a headline, is its own block. And just like physical blocks, WordPress blocks can be added, arranged, and rearranged, allowing WordPress users to create media-rich pages in a visually intuitive way -- and without work-arounds like shortcodes or custom HTML. The block editor first became available in December 2018, and we're still hard at work refining the experience, creating more and better blocks, and laying the groundwork for the next three phases of work. The Gutenberg plugin gives you the latest version of the block editor, so you can join us in testing bleeding-edge features, start playing with blocks, and maybe get inspired to build your own. -Check out the [Ways to keep up with Gutenberg & Full Site Editing (FSE)](https://make.wordpress.org/core/2020/05/20/ways-to-keep-up-with-full-site-editing-fse/) +Check out the [Keeping up with Gutenberg Index](https://make.wordpress.org/core/handbook/references/keeping-up-with-gutenberg-index/) ## Getting Started @@ -55,8 +55,6 @@ As with all WordPress projects, we want to ensure a welcoming environment for ev You can join us in the `#core-editor` channel in Slack, see the [WordPress Slack page](https://make.wordpress.org/chat/) for signup information; it is free to join. -**Weekly meetings** The Editor Team meets weekly on Wednesdays at 14:00 UTC in Slack. If you can not join the meeting, [agenda](https://make.wordpress.org/core/tag/core-editor-agenda/) and [notes](https://make.wordpress.org/core/tag/core-editor-summary/) are posted to the [Make WordPress Blog](https://make.wordpress.org/core/). - ## License WordPress is free software, and is released under the terms of the GNU General Public License version 2 or (at your option) any later version. See [LICENSE.md](LICENSE.md) for complete license. diff --git a/bin/api-docs/gen-theme-reference.js b/bin/api-docs/gen-theme-reference.js index 0ea9e282e5463..07a8c2fcc697d 100644 --- a/bin/api-docs/gen-theme-reference.js +++ b/bin/api-docs/gen-theme-reference.js @@ -127,16 +127,44 @@ const getSettingsPropertiesMarkup = ( struct ) => { } let markup = '| Property | Type | Default | Props |\n'; - markup += '| --- | --- | --- |--- |\n'; + markup += '| --- | --- | --- |--- |\n'; ks.forEach( ( key ) => { const def = 'default' in props[ key ] ? props[ key ].default : ''; - const ps = + let type = props[ key ].type || ''; + let ps = props[ key ].type === 'array' ? keys( getPropertiesFromArray( props[ key ].items ) ) .sort() .join( ', ' ) : ''; - markup += `| ${ key } | ${ props[ key ].type } | ${ def } | ${ ps } |\n`; + + /* + * Handle`oneOf` type definitions - extract the type and properties. + * See: https://json-schema.org/understanding-json-schema/reference/combining#oneOf + */ + if ( props[ key ].oneOf && Array.isArray( props[ key ].oneOf ) ) { + if ( ! type ) { + type = props[ key ].oneOf + .map( ( item ) => item.type ) + .join( ', ' ); + } + + if ( ! ps ) { + ps = props[ key ].oneOf + .map( ( item ) => + item?.type === 'object' && item?.properties + ? '_{' + + keys( getPropertiesFromArray( item ) ) + .sort() + .join( ', ' ) + + '}_' + : '' + ) + .join( ' ' ); + } + } + + markup += `| ${ key } | ${ type } | ${ def } | ${ ps } |\n`; } ); return markup; @@ -213,10 +241,13 @@ const formatType = ( prop ) => { const types = []; propTypes.forEach( ( item ) => { - if ( item.type ) types.push( item.type ); + if ( item.type ) { + types.push( item.type ); + } // refComplete is always an object - if ( item.$ref && item.$ref === '#/definitions/refComplete' ) + if ( item.$ref && item.$ref === '#/definitions/refComplete' ) { types.push( 'object' ); + } } ); type = [ ...new Set( types ) ].join( ', ' ); diff --git a/bin/packages/lint-staged-typecheck.js b/bin/packages/lint-staged-typecheck.js index 7b7eb7b846bfb..8e656755134f1 100644 --- a/bin/packages/lint-staged-typecheck.js +++ b/bin/packages/lint-staged-typecheck.js @@ -28,9 +28,11 @@ const changedPackages = [ fs.existsSync( path.join( packageRoot, 'tsconfig.json' ) ) ); -try { - execa.sync( tscPath, [ '--build', ...changedPackages ] ); -} catch ( err ) { - console.error( err.stdout ); - process.exitCode = 1; +if ( changedPackages.length > 0 ) { + try { + execa.sync( tscPath, [ '--build', ...changedPackages ] ); + } catch ( err ) { + console.error( err.stdout ); + process.exitCode = 1; + } } diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index edb5344d60e77..9d9b39fce0984 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -63,7 +63,9 @@ function sanitizeBranchName( branch ) { * @return {number|undefined} Median value or undefined if array empty. */ function median( array ) { - if ( ! array || ! array.length ) return undefined; + if ( ! array || ! array.length ) { + return undefined; + } const numbers = [ ...array ].sort( ( a, b ) => a - b ); const middleIndex = Math.floor( numbers.length / 2 ); diff --git a/changelog.txt b/changelog.txt index 41112c1cf46c0..aee9a5c1a1575 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,583 @@ == Changelog == += 18.3.0-rc.1 = + + +## Changelog + +### Enhancements + +- Output post classes in the editor. ([60642](https://github.com/WordPress/gutenberg/pull/60642)) +- Abstract keyboard shortcuts for heading to paragraph transform and vice-versa. ([60606](https://github.com/WordPress/gutenberg/pull/60606)) +- Capitalize more occurrences of Navigation Menu. ([60747](https://github.com/WordPress/gutenberg/pull/60747)) +- Clean up top toolbar to use same metrics as block toolbar. ([61126](https://github.com/WordPress/gutenberg/pull/61126)) +- Update template and template parts labels. ([61146](https://github.com/WordPress/gutenberg/pull/61146)) +- Update the template preview menu item text in the Template option. ([57802](https://github.com/WordPress/gutenberg/pull/57802)) +- Upgrade React to v18.3. ([61202](https://github.com/WordPress/gutenberg/pull/61202)) + +#### Site Editor +- Add PluginDocumentSettingPanel in template inspector controls. ([60961](https://github.com/WordPress/gutenberg/pull/60961)) +- Add publish flow in site editor. ([61136](https://github.com/WordPress/gutenberg/pull/61136)) +- Classic Theme: Expose new Patterns page and remove Template Parts submenu. ([61080](https://github.com/WordPress/gutenberg/pull/61080)) +- Consolidate editor/canvas resize handles. ([60712](https://github.com/WordPress/gutenberg/pull/60712)) +- Patterns data view: Remove icons in favor of dedicated sync status field. ([60833](https://github.com/WordPress/gutenberg/pull/60833)) +- Patterns: Remove "Manage all parts" page & link. ([60689](https://github.com/WordPress/gutenberg/pull/60689)) +- Tweak Template/Template Parts/Page creation modal. ([61005](https://github.com/WordPress/gutenberg/pull/61005)) +- Drop patterns and blocks between sections only in zoom out mode. ([60828](https://github.com/WordPress/gutenberg/pull/60828)) +- Insert patterns at the end of the root section. ([60855](https://github.com/WordPress/gutenberg/pull/60855)) +- Typography Panel: Use simple labels. ([60886](https://github.com/WordPress/gutenberg/pull/60886)) +- Font Library: Convert heading text to heading elements and group fonts as a list. ([58834](https://github.com/WordPress/gutenberg/pull/58834)) + +#### Block Editor +- Add new `TextAlignmentControl` component. ([60841](https://github.com/WordPress/gutenberg/pull/60841)) +- Editor: Sort style overrides by block client ids. ([61039](https://github.com/WordPress/gutenberg/pull/61039)) +- Inserter: Bail early when a user has no permission to upload media. ([60983](https://github.com/WordPress/gutenberg/pull/60983)) +- List View: Use 'isMatch' for unselect the shortcut. ([61223](https://github.com/WordPress/gutenberg/pull/61223)) +- Move search into inserter tabs. ([61108](https://github.com/WordPress/gutenberg/pull/61108)) +- Polish Autocomplete popover. ([60131](https://github.com/WordPress/gutenberg/pull/60131)) + +#### Global Styles +- Add scoping of feature level selectors for custom block style variations. ([61033](https://github.com/WordPress/gutenberg/pull/61033)) +- Allow negative values for margin controls. ([60347](https://github.com/WordPress/gutenberg/pull/60347)) +- Allow opt out of certain sets of styles in the editor. ([61035](https://github.com/WordPress/gutenberg/pull/61035)) +- Filter out color and typography variations. ([60220](https://github.com/WordPress/gutenberg/pull/60220)) +- Update utils for scoping CSS selectors. ([61026](https://github.com/WordPress/gutenberg/pull/61026)) +- Add defaultFontSizes theme.json (theme.json v3). ([58409](https://github.com/WordPress/gutenberg/pull/58409)) + +#### Block Library +- Group: Remove hardcoded Group block example styles. ([61027](https://github.com/WordPress/gutenberg/pull/61027)) +- Remove `Button` component from social link block, replace with `button` element. ([61270](https://github.com/WordPress/gutenberg/pull/61270)) +- List View: Unify shortcut handlers. ([61130](https://github.com/WordPress/gutenberg/pull/61130)) +- Update list view spacing. ([60713](https://github.com/WordPress/gutenberg/pull/60713)) + +#### Post Editor +- Editor: Move around word count, post status and last edited info in page summary. ([61235](https://github.com/WordPress/gutenberg/pull/61235)) +- Editor: Update post excerpt panel with new designs. ([60894](https://github.com/WordPress/gutenberg/pull/60894)) +- Add duplicate post action. ([60637](https://github.com/WordPress/gutenberg/pull/60637)) +- Make duplicate post action available only on the plugin for now. ([61192](https://github.com/WordPress/gutenberg/pull/61192)) + +#### Data Views +- Unbox items in grid layout. ([61159](https://github.com/WordPress/gutenberg/pull/61159)) +- Update alignment of preview contents in Patterns and Templates data views. ([61190](https://github.com/WordPress/gutenberg/pull/61190)) + +#### Components +- ComboboxControl: Simplify string normalization. ([60893](https://github.com/WordPress/gutenberg/pull/60893)) +- Update help text alignment in CheckboxControl. ([60787](https://github.com/WordPress/gutenberg/pull/60787)) +- RangeControl: Remove deprecated `reducedMotion` util. ([61119](https://github.com/WordPress/gutenberg/pull/61119)) +- components - inputStyleNeutral: Remove deprecated `reducedMotion` util. ([61122](https://github.com/WordPress/gutenberg/pull/61122)) + +### Bug Fixes + +- Editor: Do not show scrollbars when closing sidebars. ([60889](https://github.com/WordPress/gutenberg/pull/60889)) +- Fix block tab spacing when few available blocks. ([61296](https://github.com/WordPress/gutenberg/pull/61296)) +- Fix pattern preview focus styles. ([60881](https://github.com/WordPress/gutenberg/pull/60881)) +- Fix styles panel header. ([61319](https://github.com/WordPress/gutenberg/pull/61319)) +- Fix typo in clear customizations text. ([61089](https://github.com/WordPress/gutenberg/pull/61089)) +- Fix: Missing format, categories, and tags on the duplicate post action. ([61194](https://github.com/WordPress/gutenberg/pull/61194)) +- Fix: Post actions in post card panel is not checking for eligibility. ([60994](https://github.com/WordPress/gutenberg/pull/60994)) +- Notices: Fix snackbar placement. ([60912](https://github.com/WordPress/gutenberg/pull/60912)) +- Setup Node action - Set arch for key value. ([61010](https://github.com/WordPress/gutenberg/pull/61010)) +- docgen: Fix qualified types with type parameters. ([61097](https://github.com/WordPress/gutenberg/pull/61097)) +- Background image: Tools panel shouldn't show reset button for inherited values. ([61304](https://github.com/WordPress/gutenberg/pull/61304)) +- Fix template files query by post-type. ([61244](https://github.com/WordPress/gutenberg/pull/61244)) +- Remove unnecessary period in template block selection notice. ([61087](https://github.com/WordPress/gutenberg/pull/61087)) +- Fix issue where pattern override values reset when saving. ([61023](https://github.com/WordPress/gutenberg/pull/61023)) +- Blocks: Merge variations bootstrapped from a server with the client definitions. ([60832](https://github.com/WordPress/gutenberg/pull/60832)) +- Bump minimum required WordPress version to 6.4. ([60780](https://github.com/WordPress/gutenberg/pull/60780)) + +#### Site Editor +- DocumentBar: Account for when top toolbar is open. ([61118](https://github.com/WordPress/gutenberg/pull/61118)) +- Don't show appender at all inside sections on zoom-out mode. ([60948](https://github.com/WordPress/gutenberg/pull/60948)) +- Editor: Avoid triggering the start page modal on unsaved pages. ([61082](https://github.com/WordPress/gutenberg/pull/61082)) +- Fix zoom out mode background color on Safari. ([60873](https://github.com/WordPress/gutenberg/pull/60873)) +- Fix: Pattern details page backpath. ([61174](https://github.com/WordPress/gutenberg/pull/61174)) +- Make the zoom out inserters work for sections inside the section root. ([60909](https://github.com/WordPress/gutenberg/pull/60909)) +- Select last section if parent section doesn't exist. ([61002](https://github.com/WordPress/gutenberg/pull/61002)) + +#### Components +- Box Control: Fix issue with negative values. ([60984](https://github.com/WordPress/gutenberg/pull/60984)) +- Fix usages of uSES with missing getServerSnapshot. ([60943](https://github.com/WordPress/gutenberg/pull/60943)) + +#### Block Library +- Avoid unnecessary heading level and paragraph transform via keyboard shortcuts. ([60955](https://github.com/WordPress/gutenberg/pull/60955)) +- Navigation: Remove unnecessary `__experimentalStyle`. ([60965](https://github.com/WordPress/gutenberg/pull/60965)) + +### Accessibility + +- Add aria-haspopup="dialog" to Enable/Disable overrides button. ([61309](https://github.com/WordPress/gutenberg/pull/61309)) +- Fix unlabeled PostURL Copy button. ([61195](https://github.com/WordPress/gutenberg/pull/61195)) +- [Data Views] User patterns: Use excerpt as description. ([60549](https://github.com/WordPress/gutenberg/pull/60549)) + +### Performance + +- Block lib: columns: Remove store subs on mount. ([61123](https://github.com/WordPress/gutenberg/pull/61123)) +- Block: Remove outline related store selecting. ([61139](https://github.com/WordPress/gutenberg/pull/61139)) +- Rich text: Combine all ref effects. ([60936](https://github.com/WordPress/gutenberg/pull/60936)) +- Template lock: Batch block disabling. ([60934](https://github.com/WordPress/gutenberg/pull/60934)) +- useBlockSync: Avoid replacing blocks twice on mount. ([60967](https://github.com/WordPress/gutenberg/pull/60967)) +- useMatchMedia: Cache queries. ([61000](https://github.com/WordPress/gutenberg/pull/61000)) +- ListViewBlock: Combine 'useSelect' hooks, part two. ([61054](https://github.com/WordPress/gutenberg/pull/61054)) +- Remove `showFixedToolbar` from `useShowBlockTools`. ([60717](https://github.com/WordPress/gutenberg/pull/60717)) +- Editor: Optimize some of the post-support panels. ([61003](https://github.com/WordPress/gutenberg/pull/61003)) +- Core data: getEditedEntityRecord: Do not return empty object. ([60988](https://github.com/WordPress/gutenberg/pull/60988)) +- Drop zone: Avoid media query on mount. ([60546](https://github.com/WordPress/gutenberg/pull/60546)) +- Nav link: Use rich text value. ([60503](https://github.com/WordPress/gutenberg/pull/60503)) + + +### Experiments + +#### Interactivity API +- Add full page client-side navigation experiment setting. ([59707](https://github.com/WordPress/gutenberg/pull/59707)) + +### Documentation + +- Add AutosaveMonitor component JSDoc enhancements. ([60905](https://github.com/WordPress/gutenberg/pull/60905)) +- Add Documentation for CharacterCount component. ([60906](https://github.com/WordPress/gutenberg/pull/60906)) +- Add EditorSnackbars component documentation. ([61110](https://github.com/WordPress/gutenberg/pull/61110)) +- Add documentation for DocumentOutline and DocumentOutlineCheck components. ([61129](https://github.com/WordPress/gutenberg/pull/61129)) +- Add documentation for EditorHistoryRedo and EditorHistoryUndo. ([60932](https://github.com/WordPress/gutenberg/pull/60932)) +- Add documentation for EditorKeyboardShortcuts and EditorKeyboardShortcutsRegister. ([60933](https://github.com/WordPress/gutenberg/pull/60933)) +- Add documentation for PostAuthor, PostAuthorCheck, PostAuthorPanel components. ([61090](https://github.com/WordPress/gutenberg/pull/61090)) +- Added doc for components PageAttributesCheck, PageAttributesPanel, PageAttributesOrder, PageAttributesParent. ([60977](https://github.com/WordPress/gutenberg/pull/60977)) +- Change the name of the Interactivity API quick start guide markdown file. ([61198](https://github.com/WordPress/gutenberg/pull/61198)) +- Docs: Fix import statement of PluginMoreMenuItem. ([60931](https://github.com/WordPress/gutenberg/pull/60931)) +- Docs: Handle "oneOf" in theme.json schema doc generation. ([61024](https://github.com/WordPress/gutenberg/pull/61024)) +- Fix import in block editor’s readme example. ([61218](https://github.com/WordPress/gutenberg/pull/61218)) +- Fix use local version of theme.json schema in bundled files. ([61312](https://github.com/WordPress/gutenberg/pull/61312)) +- FontSizerPicker: Improve documentation for default units. ([60996](https://github.com/WordPress/gutenberg/pull/60996)) +- InputControl: Added password visibility story. ([60898](https://github.com/WordPress/gutenberg/pull/60898)) +- Move iAPI documentation from package to reference guides. ([61143](https://github.com/WordPress/gutenberg/pull/61143)) +- Refresh the folder structure documentation page. ([60953](https://github.com/WordPress/gutenberg/pull/60953)) +- Small-typo-change. ([61178](https://github.com/WordPress/gutenberg/pull/61178)) +- Theme JSON: Backport PHP annotations from Core. ([61301](https://github.com/WordPress/gutenberg/pull/61301)) +- Update an anchor link in block-in-the-editor.md. ([59527](https://github.com/WordPress/gutenberg/pull/59527)) +- Update block-deprecation.md. ([60768](https://github.com/WordPress/gutenberg/pull/60768)) +- Update documentation for theme.json version 3. ([61221](https://github.com/WordPress/gutenberg/pull/61221)) +- Update main readme file with relevant current information. ([60942](https://github.com/WordPress/gutenberg/pull/60942)) + + +### Code Quality + +- DataViews: Enable types. ([61185](https://github.com/WordPress/gutenberg/pull/61185)) +- Editor: Cleanup edit-post classnames and documentation. ([61240](https://github.com/WordPress/gutenberg/pull/61240)) +- Editor: Consistently deprecate edit-post and edit-site slots. ([61134](https://github.com/WordPress/gutenberg/pull/61134)) +- Editor: Unify the BlockContextualToolbar component between post and site editors. ([61104](https://github.com/WordPress/gutenberg/pull/61104)) +- Editor: Unify the more menu. ([60910](https://github.com/WordPress/gutenberg/pull/60910)) +- Editor: Unify the region navigation keyboard shortcuts. ([60907](https://github.com/WordPress/gutenberg/pull/60907)) +- Fix: Actions moved to the editor package still reference edit-site on their ids. ([60899](https://github.com/WordPress/gutenberg/pull/60899)) +- Remove unnecessary `usesContext` from paragraph block. ([61008](https://github.com/WordPress/gutenberg/pull/61008)) +- Removed Extra Space Before Since. ([60918](https://github.com/WordPress/gutenberg/pull/60918)) +- ToolsMoreMenuGroup: Remove form post editor. ([61132](https://github.com/WordPress/gutenberg/pull/61132)) +- Unify placeholders. ([59275](https://github.com/WordPress/gutenberg/pull/59275)) +- Use `math.div` for scss division. ([61285](https://github.com/WordPress/gutenberg/pull/61285)) +- useBlockProps: Remove dead code. ([61133](https://github.com/WordPress/gutenberg/pull/61133)) +- useBlockSync: Just testing without isControlled effect. ([61114](https://github.com/WordPress/gutenberg/pull/61114)) +- Add eslint autofix commit to ignored git commits. ([61253](https://github.com/WordPress/gutenberg/pull/61253)) +- Rephrasing for accuracy and link to Core Trac ticket. ([61284](https://github.com/WordPress/gutenberg/pull/61284)) +- Blocks: Add a warning when registering variation without a name. ([61037](https://github.com/WordPress/gutenberg/pull/61037)) +- DataViews: Cleanup unused type property. ([61197](https://github.com/WordPress/gutenberg/pull/61197)) +- DataViews: More dataviews types. ([61193](https://github.com/WordPress/gutenberg/pull/61193)) +- Quality: Fix php warning error. ([61321](https://github.com/WordPress/gutenberg/pull/61321)) +- UseLocation instead of window.location.href. ([61230](https://github.com/WordPress/gutenberg/pull/61230)) +- Use contentOnly locking for pattern block. ([61227](https://github.com/WordPress/gutenberg/pull/61227)) +- Editor: No need to memorize callback in 'SwapTemplateButton'. ([61049](https://github.com/WordPress/gutenberg/pull/61049)) +- Improve data-wp-context debugging by validating it as a stringified JSON Object. ([61045](https://github.com/WordPress/gutenberg/pull/61045)) +- Theme JSON: Extract util to get valid block style variations. ([61030](https://github.com/WordPress/gutenberg/pull/61030)) +- Elements: Deprecate old block support filter callbacks. ([59538](https://github.com/WordPress/gutenberg/pull/59538)) + +#### Components +- AlignmentMatrixControl: Remove deprecated `reducedMotion` util. ([61113](https://github.com/WordPress/gutenberg/pull/61113)) +- FocalPointPicker: Remove deprecated `reducedMotion` util. ([61116](https://github.com/WordPress/gutenberg/pull/61116)) +- Navigation: Remove deprecated `reducedMotion` util. ([61117](https://github.com/WordPress/gutenberg/pull/61117)) +- Remove "experimental" designation for `CustomSelectControlV2`. ([61078](https://github.com/WordPress/gutenberg/pull/61078)) +- ToggleGroupControl: Remove deprecated `reducedMotion` util. ([61120](https://github.com/WordPress/gutenberg/pull/61120)) +- View: Fix TypeScript types. ([60919](https://github.com/WordPress/gutenberg/pull/60919)) +- components/elevation: Remove deprecated reducedMotion util. ([61115](https://github.com/WordPress/gutenberg/pull/61115)) + +#### Block Editor +- Convert Media Inserter to Tabs Pattern. ([60970](https://github.com/WordPress/gutenberg/pull/60970)) +- Obviate mousetrap around Navigation Link popover. ([61050](https://github.com/WordPress/gutenberg/pull/61050)) +- editPost: Deprecate __experimentalPluginPostExcerpt. ([61188](https://github.com/WordPress/gutenberg/pull/61188)) +- editPost: __experimentalPluginPostExcerpt return ``. ([61238](https://github.com/WordPress/gutenberg/pull/61238)) +- useBlockRefs: Use more efficient lookup map, use uSES. ([60945](https://github.com/WordPress/gutenberg/pull/60945)) +- withBlockTree: Simplify code that replaces/removes controlled blocks. ([61234](https://github.com/WordPress/gutenberg/pull/61234)) + +### Tools + +- ESLint Plugin: Handle multi-line translator comments. ([61096](https://github.com/WordPress/gutenberg/pull/61096)) + +#### Testing +- Components: Fix snapshot tests of ToggleGroupControl. ([61228](https://github.com/WordPress/gutenberg/pull/61228)) +- Fix ESLint warning in Performance test files. ([61311](https://github.com/WordPress/gutenberg/pull/61311)) +- Hotfix: Fixed failing snapshot test. ([61274](https://github.com/WordPress/gutenberg/pull/61274)) +- Update 3rd party actions within composite action. ([61211](https://github.com/WordPress/gutenberg/pull/61211)) + +#### Build Tooling +- Remove block-editor package usage from components. ([60999](https://github.com/WordPress/gutenberg/pull/60999)) +- lint-staged-typecheck: Don't run TSC when no TS project is affected. ([60998](https://github.com/WordPress/gutenberg/pull/60998)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @huubl: Output post classes in the editor. ([60642](https://github.com/WordPress/gutenberg/pull/60642)) +- @itzmekhokan: Fix typo in clear customizations text. ([61089](https://github.com/WordPress/gutenberg/pull/61089)) +- @lanresmith: Update an anchor link in block-in-the-editor.md. ([59527](https://github.com/WordPress/gutenberg/pull/59527)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @ajlende @carolinan @cbravobernal @colorful-tones @DaniGuardiola @desrosj @draganescu @ellatrix @fabiankaegy @fullofcaffeine @geriux @huubl @itzmekhokan @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @juanfra @juanmaguitar @lanresmith @MaggieCabrera @Mamaduka @mirka @ntsekouras @oandregal @ockham @ramonjd @retrofox @richtabor @SantosGuillamot @scruffian @shail-mehta @sirreal @stokesman @sunil25393 @swissspidy @t-hamano @talldan @twstokes @tyxla @youknowriad + + += 18.2.0 = + +## Changelog + +- Bump minimum required PHP version to 7.2 ([60714](https://github.com/WordPress/gutenberg/pull/60714)) + +### Enhancements + +#### Site Editor +- Add rename and trash actions to page panel. ([60232](https://github.com/WordPress/gutenberg/pull/60232)) +- Adjust frame animation profile. ([60589](https://github.com/WordPress/gutenberg/pull/60589)) +- Animate the radius of the frame. ([60415](https://github.com/WordPress/gutenberg/pull/60415)) +- Create router adapter for sidebar. ([60466](https://github.com/WordPress/gutenberg/pull/60466)) +- Improve the header animation. ([60408](https://github.com/WordPress/gutenberg/pull/60408)) +- Index view for Templates & Parts: Use `grid` layout as default. ([60069](https://github.com/WordPress/gutenberg/pull/60069)) +- Pages: Display content frame in mobile when canvas is not edit. ([60409](https://github.com/WordPress/gutenberg/pull/60409)) +- Simplify template reset language. ([60256](https://github.com/WordPress/gutenberg/pull/60256)) +- Template parts in patterns page: Add author field. ([60372](https://github.com/WordPress/gutenberg/pull/60372)) +- Template parts: Set backpath to patterns page. ([60667](https://github.com/WordPress/gutenberg/pull/60667)) +- Update hub markup and animation. ([60410](https://github.com/WordPress/gutenberg/pull/60410)) +- Update site hub action positioning. ([60511](https://github.com/WordPress/gutenberg/pull/60511)) + +#### Components +- CustomSelectControlV2: Support disabled in item types. ([60896](https://github.com/WordPress/gutenberg/pull/60896)) +- ExternalLink: Replace icon with unicode arrow. ([60255](https://github.com/WordPress/gutenberg/pull/60255)) +- InputBase: Simplify focus styles. ([60226](https://github.com/WordPress/gutenberg/pull/60226)) +- ProgressBar: Moved width to css var for perf. ([60388](https://github.com/WordPress/gutenberg/pull/60388)) +- SlotFill: Replace valtio with custom ObservableMap. ([60879](https://github.com/WordPress/gutenberg/pull/60879)) +- Tabs: Fallback to first enabled tab if no active tab Id. ([60681](https://github.com/WordPress/gutenberg/pull/60681)) +- Text: Add text-wrap: Pretty. ([60164](https://github.com/WordPress/gutenberg/pull/60164)) +- Try: Reduce checkbox size in data views. ([60475](https://github.com/WordPress/gutenberg/pull/60475)) + +#### Post Editor +- Confirm dialog: Use more descriptive text for the confirm button. ([60364](https://github.com/WordPress/gutenberg/pull/60364)) +- Editor: Add wordcount and reading time info in post card. ([60672](https://github.com/WordPress/gutenberg/pull/60672)) +- Editor: Animate opening and closing editor right sidebar. ([60561](https://github.com/WordPress/gutenberg/pull/60561)) +- Editor: Animate the inserter and list view panels. ([60665](https://github.com/WordPress/gutenberg/pull/60665)) +- Editor: Update post URL component. ([60632](https://github.com/WordPress/gutenberg/pull/60632)) +- Editor: Use the 'ConfirmDialog' component in template validation notice. ([60385](https://github.com/WordPress/gutenberg/pull/60385)) +- Enable template preview in post editor for non administrators. ([60447](https://github.com/WordPress/gutenberg/pull/60447)) +- Support insert before/after keyboard shortcuts when focus is within the list view. ([60651](https://github.com/WordPress/gutenberg/pull/60651)) +- Update publish flow. ([60456](https://github.com/WordPress/gutenberg/pull/60456)) + +#### Patterns +- Add a "All Template Parts" section. ([60775](https://github.com/WordPress/gutenberg/pull/60775)) +- Back Compat: Add Patterns submenu for WordPress 6.4. ([60804](https://github.com/WordPress/gutenberg/pull/60804)) +- Patterns page: Add edit & view revision actions to parts. ([60659](https://github.com/WordPress/gutenberg/pull/60659)) +- Remove "Template parts" sidebar group. ([60359](https://github.com/WordPress/gutenberg/pull/60359)) +- Site Editor: Support starter patterns. ([60745](https://github.com/WordPress/gutenberg/pull/60745)) + +#### Synced Patterns +- Adjust allow pattern overrides UX flow. ([60769](https://github.com/WordPress/gutenberg/pull/60769)) +- Consolidate "bound block" color and "synced" colors. ([60617](https://github.com/WordPress/gutenberg/pull/60617)) +- Improve override indication for editable blocks in synced patterns. ([60599](https://github.com/WordPress/gutenberg/pull/60599)) +- Refine rename flow for blocks with overrides. ([60234](https://github.com/WordPress/gutenberg/pull/60234)) + +#### Zoom Out +- Adjust block selection button. ([60348](https://github.com/WordPress/gutenberg/pull/60348)) +- Animation: Avoid fixed width when animating the secondary sidebar. ([60693](https://github.com/WordPress/gutenberg/pull/60693)) +- Introduce section container selection when assembling patterns (zoom out mode). ([59249](https://github.com/WordPress/gutenberg/pull/59249)) +- Update zoom out scale. ([60618](https://github.com/WordPress/gutenberg/pull/60618)) + +#### Block Library +- Add loading state on image upload in featured image, Site logo and Cover blocks. ([59519](https://github.com/WordPress/gutenberg/pull/59519)) +- File: Use HTML API to update the PDF preview label. ([60494](https://github.com/WordPress/gutenberg/pull/60494)) +- Navigation block: Add current-menu-item class for post type archive. ([57808](https://github.com/WordPress/gutenberg/pull/57808)) +- Remove block renaming control from advanced inspector controls group. ([60453](https://github.com/WordPress/gutenberg/pull/60453)) + +#### Data Views +- Add ability to display fields as a badge in grid layout. ([60284](https://github.com/WordPress/gutenberg/pull/60284)) +- Data views table row: Make checkboxes and actions visible on touch devices. ([60829](https://github.com/WordPress/gutenberg/pull/60829)) +- DataViews: Make the experiment about custom views. ([60813](https://github.com/WordPress/gutenberg/pull/60813)) + +#### Layout +- Add a Row control to grid layout in manual mode. ([60652](https://github.com/WordPress/gutenberg/pull/60652)) +- Apply negative margins for alignfull children of blocks with custom padding set. ([60716](https://github.com/WordPress/gutenberg/pull/60716)) +- Try reducing specificity of layout style selectors. ([60228](https://github.com/WordPress/gutenberg/pull/60228)) + +#### Block Editor +- Remove animation from InlineLinkUI. ([60575](https://github.com/WordPress/gutenberg/pull/60575)) +- Remove root appender. ([60697](https://github.com/WordPress/gutenberg/pull/60697)) +- Try: Add new `textAlign` block support. ([59531](https://github.com/WordPress/gutenberg/pull/59531)) + +#### Global Styles +- Background image: Display default background size value in global styles. ([60490](https://github.com/WordPress/gutenberg/pull/60490)) +- Background UI control labels. ([60264](https://github.com/WordPress/gutenberg/pull/60264)) +- Use text and button background color for color indicators. ([59514](https://github.com/WordPress/gutenberg/pull/59514)) + +#### Font Library +- Change Spinner to ProgressBar component. ([60570](https://github.com/WordPress/gutenberg/pull/60570)) + +#### Interactivity API +- Include `preact/debug` when `SCRIPT_DEBUG` is enabled. ([60514](https://github.com/WordPress/gutenberg/pull/60514)) + + +### New APIs + +#### Extensibility +- Editor: Support PluginPostStatusInfo Slot in the site editor. ([60814](https://github.com/WordPress/gutenberg/pull/60814)) +- Editor: Unify PluginMoreMenuItem API between post and site editors. ([60778](https://github.com/WordPress/gutenberg/pull/60778)) + - Fix: Use core instead of core/editor on normalizeComplementaryAreaScope. ([60821](https://github.com/WordPress/gutenberg/pull/60821)) +- Editor: Unify PluginSidebarMoreMenuItem. ([60853](https://github.com/WordPress/gutenberg/pull/60853)) +- Editor: Unify the PluginSidebar slot between post and site editors. ([60815](https://github.com/WordPress/gutenberg/pull/60815)) + + +### Bug Fixes + +- Fix: Add types to useSuspenseSelect. ([60733](https://github.com/WordPress/gutenberg/pull/60733)) +- Fix experimental useHasRecursion deprecation. ([60451](https://github.com/WordPress/gutenberg/pull/60451)) +- Fix translatable string in pagination modal. ([60742](https://github.com/WordPress/gutenberg/pull/60742)) +- Interactivity: Return useMemo and useCallback hooks. ([60474](https://github.com/WordPress/gutenberg/pull/60474)) +- Only show block icon in toolbar for contentOnly blocks when block is a synced block. ([60647](https://github.com/WordPress/gutenberg/pull/60647)) +- Patterns: Guard for unknown pattern in server-side resolver. ([60464](https://github.com/WordPress/gutenberg/pull/60464)) +- Snackbar: Make the `explicitDismiss` string translatable. ([60368](https://github.com/WordPress/gutenberg/pull/60368)) +- Update standardisation of 'Navigation Menu' to have both words capitalised in user-facing menus. ([60262](https://github.com/WordPress/gutenberg/pull/60262)) + - Standardise capitalisation of Navigation Menu in sidebar. ([60527](https://github.com/WordPress/gutenberg/pull/60527)) + +#### Block Library +- Embed: Avoid retrying valid URLs. ([60655](https://github.com/WordPress/gutenberg/pull/60655)) +- File: Mark update for setting default label as non-persistent. ([60492](https://github.com/WordPress/gutenberg/pull/60492)) +- Fix don't close overlay menu when focus leaves submenu. ([60406](https://github.com/WordPress/gutenberg/pull/60406)) +- Fix pattern block recursion handling. ([60452](https://github.com/WordPress/gutenberg/pull/60452)) +- Image: Fix cropper resize on align change (react-easy-crop upgrade). ([60581](https://github.com/WordPress/gutenberg/pull/60581)) +- Latest Posts: Remove wrapper div and apply consistent class. ([60728](https://github.com/WordPress/gutenberg/pull/60728)) +- List: Disable edit as HTML support. ([55656](https://github.com/WordPress/gutenberg/pull/55656)) +- Media & Text: Hide the alt text option for featured images. ([60496](https://github.com/WordPress/gutenberg/pull/60496)) +- Post title: Re-add the paragraph level (without UI). ([60548](https://github.com/WordPress/gutenberg/pull/60548)) +- Pullquote: Reduce specificity of padding rule to avoid conflicts with global styles. ([60649](https://github.com/WordPress/gutenberg/pull/60649)) +- Separator block: Reduce default border styles to avoid conflicts with global styles. ([60740](https://github.com/WordPress/gutenberg/pull/60740)) + +#### Site Editor +- Chore: Fix missing comma on welcome guide styles. ([60596](https://github.com/WordPress/gutenberg/pull/60596)) +- Fallback to URL when site title is empty. ([60885](https://github.com/WordPress/gutenberg/pull/60885)) +- Fix activating a theme in site editor when previewing. ([60699](https://github.com/WordPress/gutenberg/pull/60699)) +- Fix site icon animation. ([60419](https://github.com/WordPress/gutenberg/pull/60419)) +- Fix small regression on the resize handle. ([60427](https://github.com/WordPress/gutenberg/pull/60427)) +- Fix the removePropertyFromObject function throws an error if the object is null. ([60831](https://github.com/WordPress/gutenberg/pull/60831)) +- Fix: Style issue on page actions button. ([60592](https://github.com/WordPress/gutenberg/pull/60592)) +- Pattern page: Fix deps for `onActionPerformed` useCallback. ([60784](https://github.com/WordPress/gutenberg/pull/60784)) +- Remove outdated border radius animation. ([60454](https://github.com/WordPress/gutenberg/pull/60454)) +- Router: Load proper sidebar for `/wp_template`. ([60850](https://github.com/WordPress/gutenberg/pull/60850)) +- [Site Editor]: Fix ability to edit trashed pages. ([60236](https://github.com/WordPress/gutenberg/pull/60236)) + +#### Post Editor +- Editor: Do not render publish time and post status panels in design post types. ([60857](https://github.com/WordPress/gutenberg/pull/60857)) +- Editor: Fix post status label styles for low-capability users. ([60854](https://github.com/WordPress/gutenberg/pull/60854)) +- Fix display of shortcut to add non breaking space in the post editor. ([60625](https://github.com/WordPress/gutenberg/pull/60625)) +- Fix: Action button alignment on details panel. ([60773](https://github.com/WordPress/gutenberg/pull/60773)) +- Fix: Action order is different from inspector and dataviews. ([60877](https://github.com/WordPress/gutenberg/pull/60877)) +- Fix: Do not show pattern and template actions on the post editor. ([60568](https://github.com/WordPress/gutenberg/pull/60568)) +- Fix: Missing items parameter and or missing onActionPerformed calls. ([60753](https://github.com/WordPress/gutenberg/pull/60753)) +- Fix: Trash Post action and permanently delete post action do not show errors on single item. ([60597](https://github.com/WordPress/gutenberg/pull/60597)) +- Post Editor Header: Make block toolbar toggle button focus visible. ([59781](https://github.com/WordPress/gutenberg/pull/59781)) + +#### Block Editor +- Avoid errors when a block variation icon is an object. ([60766](https://github.com/WordPress/gutenberg/pull/60766)) +- Fix external link indicator in Link Control. ([60439](https://github.com/WordPress/gutenberg/pull/60439)) +- Fix for isPossibleTransformForSource handling selecting inexistent block. ([59410](https://github.com/WordPress/gutenberg/pull/59410)) +- Fix stuck dragging mode in UI in Firefox when dealing with deeply nested lists. ([60845](https://github.com/WordPress/gutenberg/pull/60845)) +- Prevents delete key from undoing automatic changes. ([60858](https://github.com/WordPress/gutenberg/pull/60858)) +- Raw Handling - msListIgnore - Check attributes are valid. ([60375](https://github.com/WordPress/gutenberg/pull/60375)) + +#### Data Views +- DataViews: Fix typing in combobox filter. ([60819](https://github.com/WordPress/gutenberg/pull/60819)) +- Fix default layout configuration in pages list. ([60407](https://github.com/WordPress/gutenberg/pull/60407)) +- Fix pattern titles. ([60640](https://github.com/WordPress/gutenberg/pull/60640)) + +#### Zoom Out +- Don't allow shuffle for locked patterns. ([60381](https://github.com/WordPress/gutenberg/pull/60381)) +- Prevent exiting Zoom Out mode from stealing focus. ([60441](https://github.com/WordPress/gutenberg/pull/60441)) +- Respect reduced motion when engaging zoom out mode. ([60808](https://github.com/WordPress/gutenberg/pull/60808)) + +#### Font Library +- Avoid overriding custom settings on font library save. ([60438](https://github.com/WordPress/gutenberg/pull/60438)) +- Fix modal scrollbar. ([60641](https://github.com/WordPress/gutenberg/pull/60641)) +- I18N: Add context to 'Library' string. ([60520](https://github.com/WordPress/gutenberg/pull/60520)) + +#### Components +- Fix link control link preview when it displays long URLs. ([60890](https://github.com/WordPress/gutenberg/pull/60890)) +- ProgressBar: Fix CSS variable with invalid value. ([60576](https://github.com/WordPress/gutenberg/pull/60576)) + +#### Layout +- Always add semantic classes. ([60668](https://github.com/WordPress/gutenberg/pull/60668)) +- Don't output base flow and constrained layout rules on themes without theme.json. ([60764](https://github.com/WordPress/gutenberg/pull/60764)) +- Fix responsive column span logic on the front end. ([60976](https://github.com/WordPress/gutenberg/pull/60976)) +- Restore classic auto margin rule to its previous specificity. ([60802](https://github.com/WordPress/gutenberg/pull/60802)) + +#### Interactivity API +- Allow multiple event handlers for the same type with `data-wp-on`. ([60661](https://github.com/WordPress/gutenberg/pull/60661)) +- Update the query block to permit non-core interactive blocks. ([60006](https://github.com/WordPress/gutenberg/pull/60006)) + +#### Templates API +- Fix static posts page setting resolved template. ([60608](https://github.com/WordPress/gutenberg/pull/60608)) +- Fix: Honor 'template_hierarchy' filters on template fallbacks. ([60377](https://github.com/WordPress/gutenberg/pull/60377)) + +#### Distraction Free +- Only show inserter in document tools if DFM is off. ([60426](https://github.com/WordPress/gutenberg/pull/60426)) +- Remove alpha from edit post header. ([60431](https://github.com/WordPress/gutenberg/pull/60431)) + +#### REST API +- Fix PHP notice triggered by 'gutenberg_update_initial_settings'. ([60862](https://github.com/WordPress/gutenberg/pull/60862)) + +#### Global Styles +- Editor styles: Delete duplicate backwards compat CSS custom properties. ([60400](https://github.com/WordPress/gutenberg/pull/60400)) +- Fix browser warning regarding highlight colors. ([60555](https://github.com/WordPress/gutenberg/pull/60555)) + +### Accessibility + +- Details Block: remove `overflow:Hidden` style. ([60270](https://github.com/WordPress/gutenberg/pull/60270)) +- Do not render pattern aria description if not button is rendered. ([60653](https://github.com/WordPress/gutenberg/pull/60653)) +- Fix inserter pattern pagination focus loss. ([60620](https://github.com/WordPress/gutenberg/pull/60620)) +- Make sure Social icons links aren't empty and improve UI clarity. ([60047](https://github.com/WordPress/gutenberg/pull/60047)) + +### Performance + +- Add null check to prevent errors in `get_block_template` filter. ([60491](https://github.com/WordPress/gutenberg/pull/60491)) +- Block preview: Build in async rendering. ([60425](https://github.com/WordPress/gutenberg/pull/60425)) +- Editor canvas: Reduces resize listeners. ([60682](https://github.com/WordPress/gutenberg/pull/60682)) +- Layout support: Avoid two block editor store subs. ([60612](https://github.com/WordPress/gutenberg/pull/60612)) +- Optimize the rendering of the EditorStyles component. ([60493](https://github.com/WordPress/gutenberg/pull/60493)) +- Post Title: Avoid accidental types requests. ([60531](https://github.com/WordPress/gutenberg/pull/60531)) +- Preview: Skip rendering rich text. ([60544](https://github.com/WordPress/gutenberg/pull/60544)) +- Previews: Avoid unneeded block selectors. ([60543](https://github.com/WordPress/gutenberg/pull/60543)) +- Site Editor: Close the editor sidebar by default. ([60820](https://github.com/WordPress/gutenberg/pull/60820)) +- Zoom-out: Scale should be stable function. ([60580](https://github.com/WordPress/gutenberg/pull/60580)) +- getEntityRecords: Batch actions. ([60591](https://github.com/WordPress/gutenberg/pull/60591)) + +#### Block Library +- Avoid calling getBlocks selector for navigation link blocks. ([60458](https://github.com/WordPress/gutenberg/pull/60458)) +- Image cropper: Remove clientWidth prop with useResizeObserver. ([60674](https://github.com/WordPress/gutenberg/pull/60674)) +- Navigation block: Avoid selector + style recalc on mount. ([60572](https://github.com/WordPress/gutenberg/pull/60572)) +- Pattern block: Avoid fetching all reusable blocks on mount. ([60310](https://github.com/WordPress/gutenberg/pull/60310)) +- Post Featured Image: Optimize store subscriptions. ([60770](https://github.com/WordPress/gutenberg/pull/60770)) + +#### Site Editor +- Optimize the AddTemplate component used in data views pages. ([60586](https://github.com/WordPress/gutenberg/pull/60586)) +- Sidebar slide animation: Replace motion.div with CSS animation. ([60849](https://github.com/WordPress/gutenberg/pull/60849)) + +### Documentation + +- Add `AutosaveMonitor` component JSDoc and populate `README` with auto-gen documentation. ([60882](https://github.com/WordPress/gutenberg/pull/60882)) +- Add documentation for `disableLineBreaks` property of `RichText`. ([56284](https://github.com/WordPress/gutenberg/pull/56284)) +- Added Documentation for PostExcerptCheck. ([60864](https://github.com/WordPress/gutenberg/pull/60864)) +- Added links to related components. ([60726](https://github.com/WordPress/gutenberg/pull/60726)) +- Correct link to the theme json reference. ([60517](https://github.com/WordPress/gutenberg/pull/60517)) +- DimensionControl: Fix story configuration. ([60703](https://github.com/WordPress/gutenberg/pull/60703)) +- Docs: Fix typos in interactivity API reference. ([60870](https://github.com/WordPress/gutenberg/pull/60870)) +- Docs: Update wording in Block Editor Handbook to reflect that all examples now use JSX. ([56315](https://github.com/WordPress/gutenberg/pull/56315)) +- Fix: Grammar typo on packages/dataviews/src/search-widget.js. ([60588](https://github.com/WordPress/gutenberg/pull/60588)) +- Fix: Link to the block building tutorial. ([60518](https://github.com/WordPress/gutenberg/pull/60518)) +- Fixes a link to the getEntityRecord documentation. ([60823](https://github.com/WordPress/gutenberg/pull/60823)) +- Improve documentation for block variation `isActive` property. ([60801](https://github.com/WordPress/gutenberg/pull/60801)) +- Update: Hardcoded documentation link to a branch that does not exist. ([60671](https://github.com/WordPress/gutenberg/pull/60671)) +- Update: Reference editor scope instead of edit-site, edit-post on interface package documentation. ([60818](https://github.com/WordPress/gutenberg/pull/60818)) +- [Create Block] Adding documentation for the transformer property. ([60445](https://github.com/WordPress/gutenberg/pull/60445)) + + +### Code Quality + +- Added @return after @global in php doc. ([60611](https://github.com/WordPress/gutenberg/pull/60611)) +- Blocks: Remove client-side polyfill for 'selectors'. ([60846](https://github.com/WordPress/gutenberg/pull/60846)) +- [Block Bindings] Don't use hooks. ([60724](https://github.com/WordPress/gutenberg/pull/60724)) +- Chore: Fix: Wrong JSDOC for an action return. ([60786](https://github.com/WordPress/gutenberg/pull/60786)) +- Chore: Simplify some CSS margin rules. ([60816](https://github.com/WordPress/gutenberg/pull/60816)) +- Editor: Serve as a proxy for the interface package. ([60748](https://github.com/WordPress/gutenberg/pull/60748)) +- Editor: Unify the auto-switch sidebars behavior. ([60869](https://github.com/WordPress/gutenberg/pull/60869)) +- Editor: Unify the keyboard shortcuts modal. ([60866](https://github.com/WordPress/gutenberg/pull/60866)) +- Editor: Unify the names of the sidebars between edit post and edit site. ([60856](https://github.com/WordPress/gutenberg/pull/60856)) +- Editor: Unify the preferences modal name. ([60871](https://github.com/WordPress/gutenberg/pull/60871)) +- Fix: Remove unused CSS for TemplatePartHint. ([60852](https://github.com/WordPress/gutenberg/pull/60852)) +- Fix: Remove unused css from page panels styles. ([60774](https://github.com/WordPress/gutenberg/pull/60774)) +- Fix: Font Library typo. ([60751](https://github.com/WordPress/gutenberg/pull/60751)) +- Fix: Remove unused CSS code from the site editor. ([60662](https://github.com/WordPress/gutenberg/pull/60662)) +- Interactivity API refactor to TypeScript (utils & kebabToCamelCase). ([60149](https://github.com/WordPress/gutenberg/pull/60149)) +- Reexport createSelector from data package. ([60370](https://github.com/WordPress/gutenberg/pull/60370)) +- Refactor: UseBlockTools cleanup. ([59450](https://github.com/WordPress/gutenberg/pull/59450)) +- Remove comment that no longer applies about appearance-tools support. ([60844](https://github.com/WordPress/gutenberg/pull/60844)) +- Reuse and unify post and page actions, accross the different use cases. ([60486](https://github.com/WordPress/gutenberg/pull/60486)) +- Test: Validate block & theme json. ([57374](https://github.com/WordPress/gutenberg/pull/57374)) +- Tests: Shard JS unit tests. ([60045](https://github.com/WordPress/gutenberg/pull/60045)) +- Tests: Share JavaScript build assets across PHP workflows. ([60428](https://github.com/WordPress/gutenberg/pull/60428)) +- Update: Avoid two useSelect calls on PostActions. ([60752](https://github.com/WordPress/gutenberg/pull/60752)) +- Update: Make content locking related selectors private. ([60827](https://github.com/WordPress/gutenberg/pull/60827)) +- Update: Move template actions to the editor store. ([60395](https://github.com/WordPress/gutenberg/pull/60395)) +- Update: Remove keyCode usage from dataviews package. ([60585](https://github.com/WordPress/gutenberg/pull/60585)) +- Update: Use util getVariationClassName instead of computing the variation inline. ([60664](https://github.com/WordPress/gutenberg/pull/60664)) + +#### Components +- Deprecate `reduceMotion` util. ([60839](https://github.com/WordPress/gutenberg/pull/60839)) +- Navigation: Soft deprecate component. ([59182](https://github.com/WordPress/gutenberg/pull/59182)) +- NavigatorProvider: Move the same-location check to the goTo function. ([60767](https://github.com/WordPress/gutenberg/pull/60767)) +- ObservableMap: Optimize unsubscribe and add unit tests. ([60892](https://github.com/WordPress/gutenberg/pull/60892)) +- Remove CSS hack for Internet Explorer 11. ([60727](https://github.com/WordPress/gutenberg/pull/60727)) + +#### Post Editor +- Editor: Optimize the 'PostSlug' component. ([60422](https://github.com/WordPress/gutenberg/pull/60422)) +- Editor: Use hook instead of HoC in 'ThemeSupportCheck'. ([60807](https://github.com/WordPress/gutenberg/pull/60807)) +- Editor: Use hooks instead of HoCs in 'PostTrashCheck'. ([60380](https://github.com/WordPress/gutenberg/pull/60380)) +- [Editor]:Get post content in PostContentInfo component. ([60743](https://github.com/WordPress/gutenberg/pull/60743)) + +#### Data Views +- DataViews: Remove `onDetailsChange` event. ([60387](https://github.com/WordPress/gutenberg/pull/60387)) +- Rename displayAsColumnFields to columnFields API. ([60504](https://github.com/WordPress/gutenberg/pull/60504)) +- Simplify visually hidden label. ([60835](https://github.com/WordPress/gutenberg/pull/60835)) + +#### Block Editor +- Refactor Link UI States. ([59762](https://github.com/WordPress/gutenberg/pull/59762)) +- Switching pattern categories inserter to Tabs component with arrow key navigation. ([60257](https://github.com/WordPress/gutenberg/pull/60257)) + +### Tools + +- Update @talldan in codeowners file, remove from edit-widgets package. ([60800](https://github.com/WordPress/gutenberg/pull/60800)) + +#### Testing +- Add end-to-end test for activating themes in site editor. ([60707](https://github.com/WordPress/gutenberg/pull/60707)) +- Automated Testing: Update end-to-end test npm commands. ([60376](https://github.com/WordPress/gutenberg/pull/60376)) +- Fix flaky Site Editor URL navigation end-to-end test. ([60675](https://github.com/WordPress/gutenberg/pull/60675)) +- PHP unit test workflow: Try removing 7.0 and 7.1. ([60686](https://github.com/WordPress/gutenberg/pull/60686)) +- Perf: Improve way we measure template loading by adding posts. ([60516](https://github.com/WordPress/gutenberg/pull/60516)) +- Performance Tests: I'm tired of doing head math 😊. ([60509](https://github.com/WordPress/gutenberg/pull/60509)) +- Upgrade Playwright to v1.43. ([60635](https://github.com/WordPress/gutenberg/pull/60635)) +- tip: Remove unecessary delay in tests except where needed. ([60897](https://github.com/WordPress/gutenberg/pull/60897)) + +#### Build Tooling +- Dependencies: Upgrade babel. ([57311](https://github.com/WordPress/gutenberg/pull/57311)) +- Upgrade simple-git dependency. ([59915](https://github.com/WordPress/gutenberg/pull/59915)) +- Update Typescript to 5.4.5. ([60793](https://github.com/WordPress/gutenberg/pull/60793)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @asheshmagar: Font library: Fix typo. ([60751](https://github.com/WordPress/gutenberg/pull/60751)) +- @DaniGuardiola: ProgressBar: Moved width to CSS var for performance. ([60388](https://github.com/WordPress/gutenberg/pull/60388)) +- @garridinsi: Interactivity API refactor to TypeScript (utils & kebabToCamelCase). ([60149](https://github.com/WordPress/gutenberg/pull/60149)) +- @xhemals: Update standardization of the 'Navigation Menu' to have both words capitalized in user-facing menus. ([60262](https://github.com/WordPress/gutenberg/pull/60262)), Standardise capitalization of the Navigation Menu in the sidebar. ([60527](https://github.com/WordPress/gutenberg/pull/60527)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @andrewserong @arthur791004 @artpi @asheshmagar @atachibana @carolinan @colinduwe @colorful-tones @DaniGuardiola @DAreRodz @draganescu @ellatrix @fabiankaegy @flexseth @garridinsi @geriux @getdave @ixkaito @jameskoster @jasmussen @jeryj @johnhooks @jorgefilipecosta @jsnajdr @kevin940726 @Mamaduka @matiasbenedetto @mhkuu @mikachan @mirka @noisysocks @ntsekouras @oandregal @ocean90 @okmttdhr @prajapatisagar @ramonjd @richtabor @ryanwelcher @scruffian @shail-mehta @shimotmk @simonhammes @sirreal @Soean @sunil25393 @t-hamano @talldan @tellthemachines @tyxla @xhemals @youknowriad + + = 18.1.2 = ## Bug Fixes diff --git a/docs/contributors/code/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md index 63114073b1b80..06f86715a65a0 100644 --- a/docs/contributors/code/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -39,13 +39,13 @@ Components may be assigned with class names that indicate states (for example, a **Example:** -Consider again the Notices example. We may want to apply specific styling for dismissible notices. The [`classnames` package](https://www.npmjs.com/package/classnames) can be a helpful utility for conditionally applying modifier class names. +Consider again the Notices example. We may want to apply specific styling for dismissible notices. The [`clsx` package](https://www.npmjs.com/package/clsx) can be a helpful utility for conditionally applying modifier class names. ```jsx -import classnames from 'classnames'; +import clsx from 'clsx'; export default function Notice( { children, onRemove, isDismissible } ) { - const classes = classnames( 'components-notice', { + const classes = clsx( 'components-notice', { 'is-dismissible': isDismissible, } ); diff --git a/docs/contributors/folder-structure.md b/docs/contributors/folder-structure.md index 98eccf1b2029b..cbad182a58f44 100644 --- a/docs/contributors/folder-structure.md +++ b/docs/contributors/folder-structure.md @@ -75,9 +75,19 @@ The following snippet explains how the Gutenberg repository is structured omitti ├── docs/*.md │ Set of documentation pages composing the [Block editor handbook](https://developer.wordpress.org/block-editor/). │ + ├── platform-docs + │ Documentation website targetted to non WordPress developpers + │ using Gutenberg in their own applications. + │ Deployed on [https://wordpress.org/gutenberg-framework/](https://wordpress.org/gutenberg-framework/). + │ + │ ├── lib │ PHP Source code of the Gutenberg plugin. │ + ├── lib/compact/wordpress-x.x + │ PHP code that was include in WordPress ont the WordPrexx X.X version. + │ It is kept to ensure plugin compatibility with older WordPress versions. + │ ├── packages │ Source code of the WordPress packages. │ Packages can be: @@ -107,10 +117,6 @@ The following snippet explains how the Gutenberg repository is structured omitti ├── packages/{packageName}/src/**/{ComponentName}/stories/*.js │ Component Stories to load on the Gutenberg storybook. │ - ├── packages/e2e-tests - │ End-2-end tests of the Gutenberg plugin. - │ Distributed as a package for potential reuse in Core and other plugins. - │ ├── phpunit │ Unit tests for the PHP code of the Gutenberg plugin. │ @@ -123,11 +129,17 @@ The following snippet explains how the Gutenberg repository is structured omitti ├── test/native │ Configuration for the Gutenberg Mobile unit tests. │ - └── test/unit + ├── test/unit │ Configuration for the Packages unit tests. │ - └── tools/eslint + ├── test/e2e + │ End-2-end tests of the Gutenberg plugin. + │ + ├── test/performance + │ Performance metrics. Results are tracked on the [Gutenberg performance dashboard](https://codevitals.run/project/gutenberg). + │ + ├── tools/eslint │ Configuration files for the ESLint linter. │ - └── tools/webpack + ├── tools/webpack │ Configuration files for the webpack build. diff --git a/docs/getting-started/fundamentals/block-in-the-editor.md b/docs/getting-started/fundamentals/block-in-the-editor.md index 85a6dfe506db3..f7357def5ec2d 100644 --- a/docs/getting-started/fundamentals/block-in-the-editor.md +++ b/docs/getting-started/fundamentals/block-in-the-editor.md @@ -1,6 +1,6 @@ # The block in the Editor -The Block Editor is a React Single Page Application (SPA). Every block in the Editor is displayed through a React component defined in the `edit` property of the settings object used to [register the block](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-javascript-client-side) on the client. +The Block Editor is a React Single Page Application (SPA). Every block in the Editor is displayed through a React component defined in the `edit` property of the settings object used to [register the block](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registering-a-block-with-javascript-client-side) on the client. The `props` object received by the block's `Edit` React component includes: @@ -164,4 +164,4 @@ Block controls rendered in both the toolbar and sidebar will also be available w - [@wordpress/block-editor](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/) - [@wordpress/components](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) - [`InspectorControls`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md) -- [`BlockControls`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/block-controls) \ No newline at end of file +- [`BlockControls`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/block-controls) diff --git a/docs/how-to-guides/curating-the-editor-experience/patterns.md b/docs/how-to-guides/curating-the-editor-experience/patterns.md index fbe5143298cdb..abb7d131041df 100644 --- a/docs/how-to-guides/curating-the-editor-experience/patterns.md +++ b/docs/how-to-guides/curating-the-editor-experience/patterns.md @@ -79,12 +79,11 @@ With WordPress 6.0 themes can register patterns from [Pattern Directory](https:/ ```json { - "version": 2, "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] } ``` -Note that this field requires using [version 2 of theme.json](/docs/reference-guides/theme-json-reference/theme-json-living.md). The content creator will then find the respective Pattern in the inserter “Patterns” tab in the categories that match the categories from the Pattern Directory. +The content creator will then find the respective Pattern in the inserter “Patterns” tab in the categories that match the categories from the Pattern Directory. ## Additional resources diff --git a/docs/how-to-guides/curating-the-editor-experience/theme-json.md b/docs/how-to-guides/curating-the-editor-experience/theme-json.md index d373e0e81e345..d14f29e0a76f3 100644 --- a/docs/how-to-guides/curating-the-editor-experience/theme-json.md +++ b/docs/how-to-guides/curating-the-editor-experience/theme-json.md @@ -10,7 +10,7 @@ Since theme.json acts as a configuration tool, there are numerous ways to define ```json { -"version": 2, + "version": 3, "settings": { "color": { "customDuotone": true, @@ -25,7 +25,7 @@ Since theme.json acts as a configuration tool, there are numerous ways to define ```json { - "version": 2, + "version": 3, "settings": { "color": { "duotone": [ @@ -54,8 +54,7 @@ Since theme.json acts as a configuration tool, there are numerous ways to define ```json { - "schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "color": { "custom": true, @@ -89,8 +88,7 @@ Since theme.json acts as a configuration tool, there are numerous ways to define ```json { - "schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "color": { "custom": true, @@ -132,8 +130,7 @@ Continuing the examples with duotone, this means you could allow full access to ```json { - "schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "color": { "custom": true, @@ -178,8 +175,7 @@ When using theme.json in a block or classic theme, these settings will stop the ```json { - "$schema": "http://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "layout": { "contentSize": "750px" diff --git a/docs/how-to-guides/metabox.md b/docs/how-to-guides/metabox.md index efc57e9423948..d16666a060c3c 100644 --- a/docs/how-to-guides/metabox.md +++ b/docs/how-to-guides/metabox.md @@ -18,7 +18,7 @@ This guide shows how to create a block that prompts a user for a single value, a This guide assumes you are already familiar with WordPress plugins, post meta, and basic JavaScript. Review the [Getting started with JavaScript tutorial](/docs/getting-started/fundamentals/javascript-in-the-block-editor.md) for an introduction. -The guide will walk through creating a basic block, but recommended to go through the [Create Block tutorial](/docs/getting-started/devenv/get-started-with-create-block.md) for a deeper understanding of creating custom blocks. +The guide will walk through creating a basic block, but recommended to go through the [Create Block tutorial](/docs/getting-started/tutorial.md) for a deeper understanding of creating custom blocks. You will need: @@ -107,8 +107,7 @@ registerBlockType( 'myguten/meta-block', { } ); ``` -Confirm this works by creating a post and add the Meta Block. You will see your field that you can type a value in. When you save the post, either as a draft or published, the post meta value will be saved too. You can verify by -saving and reloading your draft, the form will still be filled in on reload. +Confirm this works by creating a post and add the Meta Block. You will see your field that you can type a value in. When you save the post, either as a draft or published, the post meta value will be saved too. You can verify by saving and reloading your draft, the form will still be filled in on reload. You could also confirm the data is saved by checking the database table `wp_postmeta` and confirm the new post id contains the new field data. @@ -259,3 +258,7 @@ Most PHP meta boxes should continue to work in the block editor, but some meta b - Plugins making updates to their DOM on "submit" or on "save". Please also note that if your plugin triggers a PHP warning or notice to be output on the page, this will cause the HTML document type (``) to be output incorrectly. This will cause the browser to render using "Quirks Mode", which is a compatibility layer that gets enabled when the browser doesn't know what type of document it is parsing. The block editor is not meant to work in this mode, but it can _appear_ to be working just fine. If you encounter issues such as _meta boxes overlaying the editor_ or other layout issues, please check the raw page source of your document to see that the document type definition is the first thing output on the page. There will also be a warning in the JavaScript console, noting the issue. + +## Additional resources + +- [Creating a custom block that stores post meta](https://developer.wordpress.org/news/2023/03/03/creating-a-custom-block-that-stores-post-meta/) diff --git a/docs/how-to-guides/themes/global-settings-and-styles.md b/docs/how-to-guides/themes/global-settings-and-styles.md index 37f0ee8951c3c..730595df4d4be 100644 --- a/docs/how-to-guides/themes/global-settings-and-styles.md +++ b/docs/how-to-guides/themes/global-settings-and-styles.md @@ -1,6 +1,6 @@ # Global Settings & Styles (theme.json) -WordPress 5.8 comes with [a new mechanism](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/) to configure the editor that enables a finer-grained control and introduces the first step in managing styles for future WordPress releases: the `theme.json` file. Then `theme.json` [evolved to a v2](https://make.wordpress.org/core/2022/01/08/updates-for-settings-styles-and-theme-json/) with WordPress 5.9 release. This page documents its format. +WordPress 5.8 comes with [a new mechanism](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/) to configure the editor that enables a finer-grained control and introduces the first step in managing styles for future WordPress releases: the `theme.json` file. ## Rationale @@ -48,7 +48,7 @@ To address this need, we've started to experiment with CSS Custom Properties, ak ```json { - "version": 2, + "version": 3, "settings": { "color": { "palette": [ @@ -86,7 +86,7 @@ body { ```json { - "version": 2, + "version": 3, "settings": { "custom": { "line-height": { @@ -115,7 +115,7 @@ This specification is the same for the three different origins that use this for ```json { - "version": 2, + "version": 3, "settings": {}, "styles": {}, "customTemplates": {}, @@ -125,10 +125,13 @@ This specification is the same for the three different origins that use this for ### Version -This field describes the format of the `theme.json` file. The current version is [v2](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/), [introduced in WordPress 5.9](https://make.wordpress.org/core/2022/01/08/updates-for-settings-styles-and-theme-json/). It also works with the current Gutenberg plugin. +This field describes the format of the `theme.json` file. The latest version is [version 3](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/) introduced in WordPress 6.6. -If you have used [v1](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-v1/) previously, you don’t need to update the version in the v1 file to v2, as it’ll be [migrated](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-migrations/) into v2 at runtime for you. +New versions are introduced when a breaking change needs to be made. This allows theme authors to choose when to opt-in to the breaking changes and migrate their theme.json files to the new format. +Older versions of `theme.json` are backwards-compatible and will continue to work with newer versions of WordPress and the Gutenberg plugin. However new features will be developed on the latest version. + +Follow the instructions in [migrating to newer versions](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-migrations/) for details on updating to the latest version. ### Settings @@ -145,7 +148,7 @@ The settings section has the following structure: ```json { - "version": 2, + "version": 3, "settings": { "border": { "radius": false, @@ -209,7 +212,7 @@ The settings section has the following structure: ```json { - "version": 2, + "version": 3, "settings": { "appearanceTools": false, "border": { @@ -357,7 +360,7 @@ The naming schema for the classes and the custom properties is as follows: ```json { - "version": 2, + "version": 3, "settings": { "color": { "duotone": [ @@ -532,7 +535,7 @@ For example: ```json { - "version": 2, + "version": 3, "settings": { "custom": { "baseFont": 16, @@ -577,7 +580,7 @@ Note that the name of the variable is created by adding `--` in between each nes ```json { - "version": 2, + "version": 3, "settings": { "color": { "custom": false @@ -597,7 +600,7 @@ Note that the name of the variable is created by adding `--` in between each nes ```json { - "version": 2, + "version": 3, "settings": { "blocks": { "core/button": { @@ -614,7 +617,7 @@ Note that the name of the variable is created by adding `--` in between each nes ```json { - "version": 2, + "version": 3, "settings": { "color": { "palette": [ @@ -682,7 +685,7 @@ Each block declares which style properties it exposes via the [block supports me ```json { - "version": 2, + "version": 3, "styles": { "border": { "radius": "value", @@ -761,7 +764,7 @@ Each block declares which style properties it exposes via the [block supports me ```json { - "version": 2, + "version": 3, "styles": { "border": { "color": "value", @@ -856,7 +859,7 @@ Styles found at the top-level will be enqueued using the `body` selector. ```json { - "version": 1, + "version": 3, "styles": { "color": { "text": "var(--wp--preset--color--primary)" @@ -885,7 +888,7 @@ By default, the block selector is generated based on its name such as `.wp-block ```json { - "version": 1, + "version": 3, "styles": { "color": { "text": "var(--wp--preset--color--primary)" @@ -972,7 +975,7 @@ If they're found in the top-level the element selector will be used. If they're ```json { - "version": 1, + "version": 3, "styles": { "typography": { "fontSize": "var(--wp--preset--font-size--normal)" @@ -1066,7 +1069,7 @@ For example, this is how to provide styles for the existing `plain` variation fo ```json { - "version": 2, + "version": 3, "styles":{ "blocks": { "core/quote": { @@ -1103,7 +1106,7 @@ Within this field themes can list the custom templates present in the `templates ```json { - "version": 2, + "version": 3, "customTemplates": [ { "name": "my-custom-template", @@ -1132,7 +1135,7 @@ Currently block variations exist for "header" and "footer" values of the area te ```json { - "version": 2, + "version": 3, "templateParts": [ { "name": "my-template-part", @@ -1145,22 +1148,28 @@ Currently block variations exist for "header" and "footer" values of the area te ### patterns -
Supported in WordPress from version 6.0 using version 2 of theme.json.
+
Supported in WordPress from version 6.0.
Within this field themes can list patterns to register from [Pattern Directory](https://wordpress.org/patterns/). The `patterns` field is an array of pattern `slugs` from the Pattern Directory. Pattern slugs can be extracted by the `url` in single pattern view at the Pattern Directory. For example in this url `https://wordpress.org/patterns/pattern/partner-logos` the slug is `partner-logos`. ```json { - "version": 2, + "version": 3, "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] } ``` ## Developing with theme.json -It can be difficult to remember the theme.json settings and properties while you develop, so a JSON scheme was created to help. The schema is available at https://schemas.wp.org/trunk/theme.json +It can be difficult to remember the theme.json settings and properties and which versions of WordPress support which settings while you develop, so it can be helpful to use the provided JSON schema for theme.json. + +Many code editors support JSON schema and can provide help like tooltips, autocomplete, or schema validation right in your editor. + +Theme.json schemas for each WordPress version are available at `https://schemas.wp.org/wp/{{version}}/theme.json`. For example a schema for WordPress 5.8 is available at `https://schemas.wp.org/wp/5.8/theme.json`. To ensure that you're only using features available to your users, it's best to use the oldest version that your theme supports. + +The latest schema including all the latest changes from the Gutenberg plugin is available at `https://schemas.wp.org/trunk/theme.json`. -Code editors can pick up the schema and can provide help like tooltips, autocomplete, or schema validation in the editor. To use the schema in Visual Studio Code, add `"$schema": "https://schemas.wp.org/trunk/theme.json"` to the beginning of your theme.json file. +Check your editor's documentation for JSON schema support. In Visual Studio Code, for example, you need to add `"$schema": "https://schemas.wp.org/wp/x.x/theme.json"` as a top-level property of your theme.json file, but other editors may be configured differently. ![Example using validation with schema](https://developer.wordpress.org/files/2021/11/theme-json-schema-updated.gif) @@ -1210,7 +1219,7 @@ For example: ```json { - "version": 2, + "version": 3, "settings": { "custom": { "lineHeight": { @@ -1240,7 +1249,7 @@ A few notes about this process: ```json { - "version": 2, + "version": 3, "settings": { "custom": { "line--height": { // DO NOT DO THIS @@ -1284,7 +1293,7 @@ body { ### Specificity for link colors provided by the user -In v1, when a user selected a link color for a specific block we attached a class to that block in the form of `.wp-element-` and then enqueued the following style: +In WordPress 5.8, when a user selected a link color for a specific block we attached a class to that block in the form of `.wp-element-` and then enqueued the following style: ```css .wp-element- a { color: !important; } @@ -1304,7 +1313,7 @@ For blocks that contain inner blocks, such as Group, Columns, Buttons, and Socia ```json { - "version": 2, + "version": 3, "settings": { "spacing": { "blockGap": true, diff --git a/docs/how-to-guides/themes/theme-support.md b/docs/how-to-guides/themes/theme-support.md index edf4b8e505c13..e5d6c34e4081c 100644 --- a/docs/how-to-guides/themes/theme-support.md +++ b/docs/how-to-guides/themes/theme-support.md @@ -442,7 +442,6 @@ Link support has been made stable as part of WordPress 5.8. It's `false` by defa ```json { - "version": 1, "settings": { "color": { "link": true diff --git a/docs/manifest.json b/docs/manifest.json index cf30aff19c503..a257753046355 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -491,6 +491,36 @@ "markdown_source": "../docs/reference-guides/filters/global-styles-filters.md", "parent": "filters" }, + { + "title": "Interactivity API Reference", + "slug": "interactivity-api", + "markdown_source": "../docs/reference-guides/interactivity-api/README.md", + "parent": "reference-guides" + }, + { + "title": "Quick start guide", + "slug": "iapi-quick-start-guide", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-quick-start-guide.md", + "parent": "interactivity-api" + }, + { + "title": "API Reference", + "slug": "api-reference", + "markdown_source": "../docs/reference-guides/interactivity-api/api-reference.md", + "parent": "interactivity-api" + }, + { + "title": "About the Interactivity API", + "slug": "iapi-about", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-about.md", + "parent": "interactivity-api" + }, + { + "title": "Frequently Asked Questions", + "slug": "iapi-faq", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-faq.md", + "parent": "interactivity-api" + }, { "title": "SlotFills Reference", "slug": "slotfills", @@ -564,7 +594,7 @@ "parent": "reference-guides" }, { - "title": "Theme.json Version 2", + "title": "Theme.json Version 3 Reference (latest)", "slug": "theme-json-living", "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-living.md", "parent": "theme-json-reference" @@ -576,7 +606,13 @@ "parent": "theme-json-reference" }, { - "title": "Migrating to Newer Versions", + "title": "Theme.json Version 2 Reference", + "slug": "theme-json-v2", + "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-v2.md", + "parent": "theme-json-reference" + }, + { + "title": "Migrating Theme.json to Newer Versions", "slug": "theme-json-migrations", "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-migrations.md", "parent": "theme-json-reference" @@ -1667,12 +1703,6 @@ "markdown_source": "../packages/interactivity/README.md", "parent": "packages" }, - { - "title": "API Reference", - "slug": "packages-interactivity-api-reference", - "markdown_source": "../packages/interactivity/docs/api-reference.md", - "parent": "packages-interactivity" - }, { "title": "@wordpress/interface", "slug": "packages-interface", diff --git a/docs/reference-guides/README.md b/docs/reference-guides/README.md index b9725af66f080..cc30aa8752605 100644 --- a/docs/reference-guides/README.md +++ b/docs/reference-guides/README.md @@ -39,7 +39,8 @@ ## [Theme.json Reference](/docs/reference-guides/theme-json-reference/README.md) -- [Version 2 (living reference)](/docs/reference-guides/theme-json-reference/theme-json-living.md) +- [Version 3 (latest)](/docs/reference-guides/theme-json-reference/theme-json-living.md) +- [Version 2](/docs/reference-guides/theme-json-reference/theme-json-v2.md) - [Version 1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) - [Migrating to Newer Versions](/docs/reference-guides/theme-json-reference/theme-json-migrations.md) diff --git a/docs/reference-guides/block-api/block-deprecation.md b/docs/reference-guides/block-api/block-deprecation.md index 4d69d9d46843c..0da85ee02d1ec 100644 --- a/docs/reference-guides/block-api/block-deprecation.md +++ b/docs/reference-guides/block-api/block-deprecation.md @@ -23,7 +23,7 @@ It is also important to note that if a deprecation's `save` method imports addit For blocks with multiple deprecations, it may be easier to save each deprecation to a constant with the version of the block it applies to, and then add each of these to the block's `deprecated` array. The deprecations in the array should be in reverse chronological order. This allows the block editor to attempt to apply the most recent and likely deprecations first, avoiding unnecessary and expensive processing. -### Example: +**Example** ```js const v1 = {}; @@ -59,7 +59,7 @@ Deprecations are defined on a block type as its `deprecated` property, an array It's important to note that attributes, supports, and save are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. -### Example: +**Example** ```js const { registerBlockType } = wp.blocks; @@ -104,7 +104,7 @@ In the example above we updated the markup of the block to use a `div` instead o Sometimes, you need to update the attributes set to rename or modify old attributes. -### Example: +**Example** ```js @@ -155,7 +155,7 @@ In the example above we updated the markup of the block to use a `div` instead o Situations may exist where when migrating the block we may need to add or remove innerBlocks. E.g: a block wants to migrate a title attribute to a paragraph innerBlock. -### Example: +**Example** ```js const { registerBlockType } = wp.blocks; diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index 95b1107babee6..1b0925513b3a8 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -543,7 +543,7 @@ Block type frontend script module definition. They will be enqueued only when vi It's possible to pass a script module ID registered with the [`wp_register_script_module`](https://developer.wordpress.org/reference/functions/wp_register_script_module/) function, a path to a JavaScript module relative to the `block.json` file, or a list with a mix of both ([learn more](#wpdefinedasset)). -WordPress scripts and WordPress script modules are not compatible at the moment. If frontend view assets depend on WordPress scripts, `viewScript` should be used. If they depend on WordPress script modules —the Interactivity API at this time— `viewScriptModule` should be used. In the future, it will be possible to depend on scripts from script modules. +WordPress scripts and WordPress script modules are not compatible at the moment. If frontend view assets depend on WordPress scripts, `viewScript` should be used. If they depend on WordPress script modules —the Interactivity API at this time— `viewScriptModule` should be used. [More functionality](https://core.trac.wordpress.org/ticket/60647) will gradually become available to Script Modules. _Note: Available since WordPress `6.5.0`._ diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 1c0351c77fa1e..c08869db34b48 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -51,7 +51,7 @@ Prompt visitors to take action with a button-style link. ([Source](https://githu - **Name:** core/button - **Category:** design - **Parent:** core/buttons -- **Supports:** anchor, color (background, gradients, text), interactivity (clientNavigation), shadow (), spacing (padding), typography (fontSize, lineHeight), ~~alignWide~~, ~~align~~, ~~reusable~~ +- **Supports:** anchor, color (background, gradients, text), interactivity (clientNavigation), shadow (), spacing (padding), splitting, typography (fontSize, lineHeight), ~~alignWide~~, ~~align~~, ~~reusable~~ - **Attributes:** backgroundColor, gradient, linkTarget, placeholder, rel, tagName, text, textAlign, textColor, title, type, url, width ## Buttons @@ -359,7 +359,7 @@ Introduce new sections and organize content to help visitors (and search engines - **Name:** core/heading - **Category:** text -- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, className, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight) +- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, className, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fontSize, lineHeight) - **Attributes:** content, level, placeholder, textAlign ## Home Link @@ -426,7 +426,7 @@ Create a list item. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/ - **Category:** text - **Parent:** core/list - **Allowed Blocks:** core/list -- **Supports:** interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~ +- **Supports:** interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fontSize, lineHeight), ~~className~~ - **Attributes:** content, placeholder ## Login/out @@ -531,7 +531,7 @@ Start with the basic building block of all narrative. ([Source](https://github.c - **Name:** core/paragraph - **Category:** text -- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~ +- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fontSize, lineHeight), ~~className~~ - **Attributes:** align, content, direction, dropCap, placeholder ## Pattern placeholder diff --git a/docs/reference-guides/data/data-core-keyboard-shortcuts.md b/docs/reference-guides/data/data-core-keyboard-shortcuts.md index 613c5e009e8b0..d7d5cf853f786 100644 --- a/docs/reference-guides/data/data-core-keyboard-shortcuts.md +++ b/docs/reference-guides/data/data-core-keyboard-shortcuts.md @@ -22,7 +22,7 @@ const ExampleComponent = () => { const allShortcutKeyCombinations = useSelect( ( select ) => select( keyboardShortcutsStore ).getAllShortcutKeyCombinations( - 'core/edit-post/next-region' + 'core/editor/next-region' ), [] ); @@ -77,7 +77,7 @@ const ExampleComponent = () => { const allShortcutRawKeyCombinations = useSelect( ( select ) => select( keyboardShortcutsStore ).getAllShortcutRawKeyCombinations( - 'core/edit-post/next-region' + 'core/editor/next-region' ), [] ); @@ -168,7 +168,7 @@ const ExampleComponent = () => { const shortcutAliases = useSelect( ( select ) => select( keyboardShortcutsStore ).getShortcutAliases( - 'core/edit-post/next-region' + 'core/editor/next-region' ), [] ); @@ -219,7 +219,7 @@ const ExampleComponent = () => { const shortcutDescription = useSelect( ( select ) => select( keyboardShortcutsStore ).getShortcutDescription( - 'core/edit-post/next-region' + 'core/editor/next-region' ), [] ); @@ -256,7 +256,7 @@ const ExampleComponent = () => { const { character, modifier } = useSelect( ( select ) => select( keyboardShortcutsStore ).getShortcutKeyCombination( - 'core/edit-post/next-region' + 'core/editor/next-region' ), [] ); @@ -302,16 +302,16 @@ const ExampleComponent = () => { const { display, raw, ariaLabel } = useSelect( ( select ) => { return { display: select( keyboardShortcutsStore ).getShortcutRepresentation( - 'core/edit-post/next-region' + 'core/editor/next-region' ), raw: select( keyboardShortcutsStore ).getShortcutRepresentation( - 'core/edit-post/next-region', + 'core/editor/next-region', 'raw' ), ariaLabel: select( keyboardShortcutsStore ).getShortcutRepresentation( - 'core/edit-post/next-region', + 'core/editor/next-region', 'ariaLabel' ), }; @@ -410,13 +410,13 @@ const ExampleComponent = () => { const { unregisterShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { - unregisterShortcut( 'core/edit-post/next-region' ); + unregisterShortcut( 'core/editor/next-region' ); }, [] ); const shortcut = useSelect( ( select ) => select( keyboardShortcutsStore ).getShortcutKeyCombination( - 'core/edit-post/next-region' + 'core/editor/next-region' ), [] ); diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index b80703dcc67b1..88db705f23f12 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -150,7 +150,7 @@ _Parameters_ _Returns_ -- `undefined< 'edit' >`: Current user object. +- `ET.User< 'edit' >`: Current user object. ### getDefaultTemplateId @@ -178,7 +178,7 @@ _Parameters_ _Returns_ -- `undefined< EntityRecord > | undefined`: The entity record, merged with its edits. +- `ET.Updatable< EntityRecord > | false`: The entity record, merged with its edits. ### getEmbedPreview @@ -504,7 +504,7 @@ _Parameters_ _Returns_ -- `undefined< 'edit' >[]`: Users list. +- `ET.User< 'edit' >[]`: Users list. ### hasEditsForEntityRecord diff --git a/docs/reference-guides/interactivity-api/README.md b/docs/reference-guides/interactivity-api/README.md new file mode 100644 index 0000000000000..1401c7f9bfecc --- /dev/null +++ b/docs/reference-guides/interactivity-api/README.md @@ -0,0 +1,98 @@ +# Interactivity API Reference + +The Interactivity API, [introduced in WordPress 6.5](https://make.wordpress.org/core/2024/02/19/merge-announcement-interactivity-api/), provides a standard way for developers to add interactions to the front end of their blocks. The API is also used in many Core WordPress blocks, including Search, Query, Navigation, and File. + +This standard makes it easier for developers to create rich, interactive user experiences, from simple counters or pop-ups to more complex features like instant page navigation, instant search, shopping carts, or checkouts. + +Blocks can share data, actions, and callbacks between them. This makes communication between blocks simpler and less error-prone. For example, clicking on an “add to cart” block can seamlessly update a separate “cart” block. + +For more information about the genesis of the Interactivity API, check out the [original proposal](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/). You can also review the [merge announcement](https://make.wordpress.org/core/2024/02/19/merge-announcement-interactivity-api/), the [status update post](https://make.wordpress.org/core/2023/08/15/status-update-on-the-interactivity-api/), and the official [Trac ticket](https://core.trac.wordpress.org/ticket/60356). + +
+ The Interactivity API is supported by the package @wordpress/interactivity which is bundled in WordPress Core from v6.5 +
+ +## Navigating the Interactivity API docs + +Use the following links to locate the topic you're interested in. If you have never worked with the Interactivity API before, consider reading through the following resources in the order listed. + +- **[Requirements](#requirements-of-the-interactivity-api):** Check this section before you start creating your interactive blocks with the Interactivity API. +- **[Quick Start Guide](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-quick-start-guide/):** Get a custom block using the Interactivity API up and running in less than one minute. +- **[Tutorial: A first look at the Interactivity API](https://developer.wordpress.org/news/2024/04/11/a-first-look-at-the-interactivity-api/)** This article from the [WordPress Developer Blog](https://developer.wordpress.org/news/) is a great way to get introduced to the Interactivity API. +- **[API Reference](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/):** To take a deep dive into how the API works internally, the list of Directives, and how the Store works. +- **[Docs and Examples](#docs-examples):** Additional resources to learn/read more about the Interactivity API. + +To get a deeper understanding of what the Interactivity API is or find answers to questions you may have about this standard, check the following resources: + +- **[About the Interactivity API](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-about/):** To learn more about the API Goals and the reasoning behind the use of a standard to add interactivity to blocks. +- **[Frequently Asked Questions](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-faq/):** To find responses to some frequently asked questions about the technology behind and alternatives. + + +## Requirements of the Interactivity API + +Interactivity API is included in Core in WordPress 6.5. For versions below, you'll need Gutenberg 17.5 or higher installed and activated in your WordPress installation. + +It’s also important to highlight that the block creation workflow doesn’t change, and all the [prerequisites](https://developer.wordpress.org/block-editor/getting-started/devenv/) remain the same. These include: + +- [Code Editor](https://developer.wordpress.org/block-editor/getting-started/devenv/#code-editor) +- [Node.js development tools](https://developer.wordpress.org/block-editor/getting-started/devenv/#node-js-development-tools) +- [Local WordPress environment (site)](https://developer.wordpress.org/block-editor/getting-started/devenv/#local-wordpress-environment) + +You can start creating interactions once you set up a block development environment and run WordPress 6.5+ (or Gutenberg 17.5+). + +### Code requirements + +#### Add `interactivity` support to `block.json` + +To indicate that the block [supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/) the Interactivity API features, add `"interactivity": true` to the `supports` attribute of the block's `block.json` file. + +```json +// block.json +"supports": { + "interactivity": true +}, +``` + +Refer to the [`interactivity` support property docs](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/#interactivity) to get a more detailed description of this property. + +#### Load Interactivity API Javascript code with `viewScriptModule` + +The Interactivity API provides the `@wordpress/interactivity` Script Module. JavaScript using the Interactivity API should be implemented as Script Modules so they can depend on `@wordpress/interactivity`. [Script Modules have been available since WordPress 6.5](https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/). Blocks can use [`viewScriptModule` block metadata](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script-module) to enqueue their Script Modules easily: + +```json +// block.json +{ + ... + "viewScriptModule": "file:./view.js" +} +``` + +#### Add `wp-interactive` directive to a DOM element + +To "activate" the Interactivity API in a DOM element (and its children), add the [`wp-interactive`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity/packages-interactivity-api-reference/#wp-interactive) directive to the DOM element in the block's `render.php` or `save.js` files. + + + +```html +
+ +
+``` + +Refer to the [`wp-interactive` documentation](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/#wp-interactive) for a more detailed description of this directive. + +## Docs & Examples + +Here you have some more resources to learn/read more about the Interactivity API: + +- [WordPress 6.5 Dev Note](https://make.wordpress.org/core/2024/03/04/interactivity-api-dev-note/) +- [Merge announcement](https://make.wordpress.org/core/2024/02/19/merge-announcement-interactivity-api/) +- [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/) +- [Interactivity API Discussions](https://github.com/WordPress/gutenberg/discussions/52882) +- Developer Hours sessions ([Americas](https://www.youtube.com/watch?v=RXNoyP2ZiS8&t=664s) & [APAC/EMEA](https://www.youtube.com/watch?v=6ghbrhyAcvA)) +- [wpmovies.dev](http://wpmovies.dev/) demo and its [wp-movies-demo](https://github.com/WordPress/wp-movies-demo) repo + +
+ There's a Tracking Issue opened to ease the coordination of the work related to the Interactivity API Docs: Documentation for the Interactivity API - Tracking Issue #53296 +
+ diff --git a/packages/interactivity/docs/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md similarity index 100% rename from packages/interactivity/docs/api-reference.md rename to docs/reference-guides/interactivity-api/api-reference.md diff --git a/docs/reference-guides/interactivity-api/iapi-about.md b/docs/reference-guides/interactivity-api/iapi-about.md new file mode 100644 index 0000000000000..f09ef77e6211b --- /dev/null +++ b/docs/reference-guides/interactivity-api/iapi-about.md @@ -0,0 +1,197 @@ +# About the Interactivity API + +The Interactivity API is **a [standard](#why-a-standard) system of [directives](#why-directives), based on declarative code, for [adding frontend interactivity to blocks](#api-goals)**. + +**Directives extend HTML with special attributes** that tell the Interactivity API to attach a specified behavior to a DOM element or even to transform it. For those familiar with [Alpine.js](https://alpinejs.dev/), it’s a similar approach but explicitly designed to work seamlessly with WordPress. + +## API Goals + +The main goal of the Interactivity API is to **provide a standard and simple way to handle the frontend interactivity of Gutenberg blocks**. + +A standard makes it **easier for developers to create rich, interactive user experiences**, from simple cases like counters or popups to more complex features like instant page navigation, instant search, or carts and checkouts. + +All these user experiences are technically possible right now without the Interactivity API. However, the more complex the user experience and the more blocks interact with each other, the harder it becomes for developers to build and maintain sites. There are a lot of challenges they have to figure out themselves. The API aims to provide out-of-the-box means for supporting these kinds of interactions. + +To address this challenge the following requirements/goals for the Interactivity API were defined: + +- **Block-first and PHP-first**: The API must work well with PHP and the current block system, including dynamic blocks, widely extended in WordPress. It must support server-side rendering. Server-rendered HTML and client-hydrated HTML must be exactly the same. This is important for SEO and the user experience. +- **Backward compatible**: The API must be compatible with WordPress hooks, which could, for example, modify server-rendered HTML. It must also be compatible with internationalization and existing JS libraries on the site (such as jQuery). +- **Optional and gradual adoption**: Related to the previous point, the API must remain optional. It should be possible to adopt it gradually, meaning that interactive blocks not using this API can coexist with those using it. +- **Declarative and reactive**: The API must use declarative code, listen to changes in the data, and update only the parts of the DOM that depend on that data. +- **Performant**: The runtime must be fast and lightweight to ensure the best user experience. +- **Extensible**: In the same way WordPress focuses on extensibility, this new system must provide extensibility patterns to cover most use cases. +- **Atomic and composable**: Having small reusable parts that can be combined to create more complex systems is required to create flexible and scalable solutions. +- **Compatible with the existing block development tooling**: The API must be integrated with the existing block-building tools without requiring additional tooling or configuration by the developer. + +Apart from all these requirements, integrating **client-side navigation** on top of any solution should be easy and performant. Client-side navigation is the process of navigating between site pages without reloading the entire page, which is one of the most impressive user experiences demanded by web developers. For that reason, this functionality should be compatible with this new system. + +## Why directives? + +Directives are the result of deep [research into different possibilities and approaches](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-faq/#what-approaches-have-been-considered-instead-of-using-directives). We’ve found that this design covers the requirements most effectively. + +### Block-first and PHP-friendly + +The API is designed for the world of blocks and takes WordPress history of being closely attached to web standards to heart. + +As directives are HTML attributes, they are perfect for dynamic blocks and PHP. + +_Dynamic block example_ +```html +
false ) ); ?> + data-wp-watch="callbacks.logIsOpen" +> + + +

+ This element is now visible! +

+
+``` + +As you can see, directives like `data-wp-on--click` or `data-wp-show` are added as custom HTML attributes. WordPress can process this HTML on the server, handling the directives’ logic and creating the appropriate markup. + +### Backward compatible + +As the Interactivity API works perfectly with server-side rendering, you can use all the WordPress APIs, including: + +- **WordPress filters and actions**: You can keep using WordPress hooks to modify the HTML or even to modify directives. Additionally, existing hooks will keep working as expected. +- **Core Translation API**: e.g. `__()` and `_e()`. You can use it to translate the text in the HTML (as you normally would) and even use those APIs on the server side of your directives. + +### Optional and gradual adoption + +The Interactivity API pipeline promotes **progressive enhancement** by building on top of WordPress’s solid foundation and patterns. + +For example, blocks with directives can coexist with other (interactive or non-interactive) blocks. This means that if there are other blocks on the page using other frameworks like jQuery, everything will work as expected. + +
+ Full-page client-side navigation with the Interactivity API will be an exception to this compatibility with other libraries rule. See Client-side navigation for more details. +
+ +### Declarative and reactive + +The Interactivity API follows an approach similar to other popular JS frameworks by separating state, actions, and callbacks and defining them declaratively. Why declaratively? + +Declarative code describes **what** a program should do, while imperative code describes **how** the program should do it. Using a declarative approach, the UI automatically updates in response to changes in the underlying data. With an imperative approach, you must manually update the UI whenever the data changes. Compare the two code examples: + +_Imperative code_ + +```html + +

This element is now visible!

+ +``` + +_Declarative code_ + +This is the same use case shared above but serves as an example of declarative code using this new system. The JavaScript logic is defined in the `view.js` file of the block, and add the directives to the markup in the `render.php`. + +```js +// view.js file + +import { store, getContext } from "@wordpress/interactivity"; + +store( 'wpmovies', { + actions: { + toggle: () => { + const context = getContext(); + context.isOpen = !context.isOpen; + }, + }, +}); +``` + +```php + + +
true ) ); ?> +> + + +

+ This element is now visible! +

+
+``` + +Using imperative code may be easier when creating simple user experiences, but it becomes much more difficult as applications become more complex. The Interactivity API must cover all use cases, from the simplest to the most challenging. That’s why a declarative approach using directives better fits the Interactivity API. + +### Performant + +The API has been designed to be as performant as possible: + +- **The runtime code needed for the directives is just ~10 KB**, and it only needs to be loaded once for all the blocks. +- **The scripts will load without blocking the page rendering**. + +### Extensible + +Directives can be added, removed, or modified directly from the HTML. For example, users could use the [`render_block` filter](https://developer.wordpress.org/reference/hooks/render_block/) to modify the HTML and its behavior. + +In addition to using built-in directives, users can create custom directives to add any custom behaviors to their HTML. + +### Atomic and composable + +Each directive controls a small part of the DOM, and you can combine multiple directives to create rich, interactive user experiences. + +### Compatible with the existing block development tooling + +Using built-in directives does not require a build step and only requires a small runtime. A build step is necessary only when creating custom directives that return JSX. For such use cases, the API works out of the box with common block-building tools like [`wp-scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/). + +### Client-side navigation + +The Interactivity API comes with built-in primitives for adding client-side navigation to your site. This functionality is completely optional, but it opens the possibility to create these user experiences without having to opt out of the WordPress rendering system. + +
+ Full-page client-side navigation with the Interactivity API is still a work in progress (see #60951). Still, it is expected that all the interactive blocks will have to use the Interactivity API to enable full-page client-side navigation with the Interactivity API. Only in this case, the Interactivity API won't be fully compatible with other libraries (such as jQuery). +
+ +It also pairs very well with the [View Transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) allowing developers to animate page transitions easily. + +## Why a standard? + +Blocks using the Interactivity API and interactive blocks using other approaches like jQuery can coexist, and everything will work as expected. However, the Interactivity API comes with some benefits for your interactive blocks: + +- **Blocks can communicate with each other easily**. With a standard, this communication is handled by default. When different blocks use different approaches to frontend interactivity, inter-block communication becomes more complex and almost impossible when different developers create blocks. +- **Composability and compatibility**: You can combine interactive blocks, and nest them in structures with defined behaviors. Thanks to following the same standard, they are fully cross-compatible. If each block used a different approach to interactivity, they would likely break. +- **Fewer KBs will be sent to the browser**. If each plugin author uses a different JS framework, more code will be loaded in the front end. If all the blocks use the same one, the code is reused. +- If all the blocks on a page use this standard, **site-wide features like client-side navigation can be enabled**. + +Additionally, with a standard, **WordPress can absorb the maximum amount of complexity from the developer** because it will handle most of what’s needed to create an interactive block. + +_Complexities absorbed by the standard_ + +Two columns table comparing some aspects with and without a standard. Without a standard, block developers have to take care of everything, while having a standard. Totally handled by the standard: Tooling, hydration, integrating it with WordPress, SSR of the interactive parts, inter-block communication, and frontend performance. Partially handled: Security, accessibility, and best practices. Developer responsibility: Block logic. In the without a standard column, everything is under the developer responsability. + + +With this absorption, less knowledge is required to create interactive blocks, and developers have fewer decisions to worry about. + +By adopting a standard, learning from other interactive blocks is simpler, and fosters collaboration and code reusability. As a result, the development process is leanier and friendlier to less experienced developers. + diff --git a/docs/reference-guides/interactivity-api/iapi-faq.md b/docs/reference-guides/interactivity-api/iapi-faq.md new file mode 100644 index 0000000000000..2e0e0ce4da430 --- /dev/null +++ b/docs/reference-guides/interactivity-api/iapi-faq.md @@ -0,0 +1,128 @@ +# Frequently Asked Questions + +## How does the Interactivity API work under the hood? + +Its three main components are: + +- [Preact](https://preactjs.com/) combined with [Preact Signals](https://preactjs.com/guide/v10/signals/) for hydration, client logic, and client-side navigation. +- HTML Directives that can be understood by both the client and server. +- Server-side logic, handled by the [HTML_Tag_Processor](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/). + +## Why Preact to build the directives system? Why not React or another JavaScript framework? + +Preact has a number of advantages over React and other JavaScript frameworks like Vue, Svelte, or Solid in the context of the frontend (which is the focus of the Interactivity API): + +- It’s small: 8kB, including [hooks](https://preactjs.com/guide/v10/hooks/) and [signals](https://preactjs.com/blog/introducing-signals/). +- It gives us DOM diffing out of the box. +- It’s extremely extensible through their Option Hooks. They use that extensibility for the hooks (preact/hooks), compatibility with React (preact/compat) and their signals (@preact/signals). Basically, everything but the DOM diffing algorithm. +- Its core team has been great and very helpful. They are also interested in enhancing this “island-based” usage of Preact. + +## Is Gutenberg going to move from React to Preact since the Interactivity API uses it? + +No. At the moment, there are no plans to make that transition. The requirements and advantages of the editor, as a fully interactive application, are quite different. Preact does have a [`@preact/compat`](https://preactjs.com/guide/v10/switching-to-preact/) package that enables full compatibility with the React ecosystem, and many large web applications use it. However, using Preact in the block editor would not offer advantages like it does on the frontend in the Interactivity API. + +## What approaches have been considered instead of using directives? + +Many alternative approaches were considered. Here’s a brief summary of some of them: + +### React and other JavaScript frameworks + +React was considered first because Gutenberg developers are familiar with it. Other popular JS frameworks like Svelte, Vue.js, or Angular were also considered, but none of them (including React) are PHP-friendly or compatible with WordPress hooks or internationalization. + +### Alpine.js + +Alpine.js is a great framework, and it inspired a lot of functionality in the Interactivity API. However, it doesn’t support server-side rendering of its [directives](https://github.com/alpinejs/alpine/tree/d7f9d641f7a763c56c598d118bd189a406a22383/packages/docs/src/en/directives), and having a similar system tailored for WordPress blocks has many benefits. + +### Plain JavaScript + +See the answer below. + +### Template DSL + +The possibility of creating a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for writing interactive templates was also researched. The code written in that Template DSL would then be compiled into both JavaScript and PHP. However, creating a production-grade Template compiler is complex and would be a large and risky investment of effort. This approach is still being considered for the future, with the directives serving as a compilation target. + +## Why should I, as a block developer, use the Interactivity API rather than React? + +Using React on the frontend doesn’t work smoothly with server rendering in PHP. Every approach that uses React to render blocks has to load content using client-side JavaScript. If you only render your blocks on the client, it typically results in a poor user experience because the user stares at empty placeholders and spinners while waiting for content to load. + +Using JS in PHP extensions (like v8js) is also possible, but unfortunately PHP extensions are not backward compatible and can only be used when there's a PHP fallback. + +Now, it’s possible to server-render a block in PHP **and** use React to render the same block on the frontend. However, this results in a poor developer experience because the logic has to be duplicated across the PHP and React parts. Not only that, but you have now exposed yourself to subtle bugs caused by WordPress hooks! + +Imagine installing a third-party plugin with a hook (filter) that modifies the server-rendered HTML. Let’s say this filter adds a single CSS class to your block’s HTML. That CSS class will be present in the server-rendered markup. On the frontend, your block will render again in React, but now the content will not include that CSS class because there is no way to apply WordPress hooks to React-rendered content! + +On the other hand, the Interactivity API is designed to work perfectly with WordPress hooks because directives enhance the server-rendered HTML with behaviors. This also means it works out of the box with WordPress backend APIs like i18n. + +To summarize, using the Interactivity API rather than just using React comes with these benefits: + +- If you use React, your interactive blocks must generate the same markup on the client as they do on the server in PHP. Using the Interactivity API, there is no such requirement as directives are added to server-rendered HTML. +- The Interactivity API is PHP-friendlier. It works out of the box with WordPress hooks or other server functionalities such as internationalization. For example, with React, you can’t know which hooks are applied on the server, and their modifications would be overwritten after hydration. +- All the benefits of [using a standard](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/what-is-interactivity-api#why-a-standard). + + +## What are the benefits of Interactivity API over just using jQuery or vanilla JavaScript? + +The main difference is that the Interactivity API is **declarative and reactive**, so writing and maintaining complex interactive experiences should become way easier. Additionally, it has been **specially designed to work with blocks**, providing a standard that comes with the benefits mentioned above, like inter-block communication, compatibility, or site-wide features such as client-side navigation. + +Finally, comparing it with jQuery, **the Interactivity API runtime is ~10kb**, which is much more lightweight. Actually, there is an ongoing effort to remove heavy frameworks like jQuery across the WordPress ecosystem, and this would help in this regard. + +## Do I need to know React, PHP, and this Interactivity API? + +If you want to add frontend interactivity to your blocks using this API, the short answer is yes. If your block is not interactive, the block creation workflow will remain exactly the same. + +The Interactivity API introduces a new standard method to facilitate the integration of interactive behaviors into the frontend part of WordPress. This means that you still need to use React to handle the editor part of your blocks. + +On the other hand, if you want to create an interactive block, with the Interactivity API you don’t have to deal with complex topics like tooling, integration with WordPress, inter-block communication, or the server-side rendering of the interactive parts. + +## Can the Interactivity API be used beyond a block? + +Absolutely, yes, it is not limited to blocks. You'll see a lot of mentions of how the Interactivity API provides a standard for creating interactive blocks, but that's only because that's the most common use case. More generally speaking, the Interactivity API standard can be used to add "interactive behaviors" to the front end of any part of WordPress. + +See the [`wp_interactivity_process_directives` function](https://developer.wordpress.org/reference/functions/wp_interactivity_process_directives/) for details on using the Interactivity API outside of blocks with arbitrary HTML. + +## Does this mean I must migrate all my interactive blocks to use this API? + +No. Blocks outside the Interactivity API can coexist with blocks using it. However, as explained above, keep in mind that there are some benefits for blocks that use the API: + +- **Blocks can communicate with each other easily**. With a standard, this communication is handled by default. When different blocks use different approaches to frontend interactivity, inter-block communication becomes more complex and gets almost impossible when separate developers create blocks. +- **Composability and compatibility**: You can combine interactive blocks, nest them in structures with defined behaviors, and, thanks to following the same standard, they are fully cross-compatible. If each block were to use a different approach to interactivity, they would likely break. +- **Fewer KBs will be sent to the browser**. If each plugin author uses a different JS framework, more code will be loaded in the frontend. If all the blocks use the same one, the code is reused. +- If all the blocks on a page use this standard, site-wide features like client-side navigation can be enabled. + +## What are the performance implications of using this API? Is it worth loading the Interactivity API for very simple use cases? + +The API has been designed with performance in mind, so it shouldn’t be a problem: + +- **The runtime code needed for the directives is just ~10 KB**, and it only needs to be loaded once for all the blocks. +- **All the script modules that belong to the Interactivity API (including the `view.js` files) will load without blocking the page rendering.** +- There are [ongoing explorations](https://github.com/WordPress/gutenberg/discussions/52723) about the possibility of **delaying the scripts loading once the block is in the viewport**. This way, the initial load would be optimized without affecting the user experience. + +## Does it work with the Core Translation API? + +As the Interactivity API works perfectly with server-side rendering, you can use all the WordPress APIs including [`__()`](https://developer.wordpress.org/reference/functions/__/) and [`_e()`](https://developer.wordpress.org/reference/functions/_e/). You can use it to translate the text in the HTML (as you normally would) and even use it inside the store when using `wp_initial_state()` on the server side. It might look something like this: + +```php +// render.php +wp_interactivity_state( 'favoriteMovies', array( + "1" => array( + "id" => "123-abc", + "movieName" => __("someMovieName", "textdomain") + ), +) ); +``` + +A translation API compatible with script modules (needed for the Interactivity API) is +currently being worked on. Check [#60234](https://core.trac.wordpress.org/ticket/60234) to follow the progress on this work. + +## I’m concerned about XSS; can JavaScript be injected into directives? + +No. The Interactivity API only allows for [References](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/#values-of-directives-are-references-to-store-properties) to be passed as values to the directives. This way, there is no need to eval() full JavaScript expressions, so it’s not possible to perform XSS attacks. + +## Does this work with Custom Security Policies? + +Yes. The Interactivity API does not use [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) or the [`Function()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) constructor, so it doesn’t violate the [`unsafe-eval`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#unsafe_keyword_values) content security policy. It is also designed to work with any [custom content security policy](https://developer.wordpress.org/apis/security/). + +## Can you use directives to make AJAX/REST-API requests? + +Sure. Actions and callbacks called by directives can do anything a JavaScript function can, including making API requests. + diff --git a/docs/reference-guides/interactivity-api/iapi-quick-start-guide.md b/docs/reference-guides/interactivity-api/iapi-quick-start-guide.md new file mode 100644 index 0000000000000..496ad3015ca06 --- /dev/null +++ b/docs/reference-guides/interactivity-api/iapi-quick-start-guide.md @@ -0,0 +1,41 @@ +# Quick start guide + +This guide will help you build a basic block that demonstrates the Interactivity API in WordPress. + +## Scaffold an interactive block + +Start by ensuring you have Node.js and `npm` installed on your computer. Review the [Node.js development environment](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development-environment/) guide if not. + +Next, use the [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package and the [`@wordpress/create-block-interactive-template`](https://www.npmjs.com/package/@wordpress/create-block-interactive-template) template to scaffold the complete “My First Interactive Block” plugin. + +Choose the folder where you want to create the plugin, and then execute the following command in the terminal from within that folder: + +``` +npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template +``` + +The slug provided (`my-first-interactive-block`) defines the folder name for the scaffolded plugin and the internal block name. + +## Basic usage + +With the plugin activated, you can explore how the block works. Use the following command to move into the newly created plugin folder and start the development process. + +``` +cd my-first-interactive-block && npm start +``` + +When `create-block` scaffolds the block, it installs `wp-scripts` and adds the most common scripts to the block’s `package.json` file. Refer to the [Get started with wp-scripts](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/) article for an introduction to this package. + +The `npm start` command will start a development server and watch for changes in the block’s code, rebuilding the block whenever modifications are made. + +When you are finished making changes, run the `npm run build` command. This optimizes the block code and makes it production-ready. + +## View the block in action + +If you have a local WordPress installation already running, you can launch the commands above inside the `plugins` folder of that installation. If not, you can use [`wp-now`](https://github.com/WordPress/playground-tools/tree/trunk/packages/wp-now) to launch a WordPress site with the plugin installed by executing the following command from the plugin's folder (`my-first-interactive-block`). + +``` +npx @wp-now/wp-now start +``` + +You should be able to insert the "My First Interactive Block" block into any post and see how it behaves in the front end when published. diff --git a/docs/reference-guides/slotfills/plugin-more-menu-item.md b/docs/reference-guides/slotfills/plugin-more-menu-item.md index 6a53c029b10dd..b5dd211b60f3e 100644 --- a/docs/reference-guides/slotfills/plugin-more-menu-item.md +++ b/docs/reference-guides/slotfills/plugin-more-menu-item.md @@ -6,7 +6,7 @@ This slot will add a new item to the More Tools & Options section. ```js import { registerPlugin } from '@wordpress/plugins'; -import { PluginMoreMenuItem } from '@wordpress/edit-post'; +import { PluginMoreMenuItem } from '@wordpress/editor'; import { image } from '@wordpress/icons'; const MyButtonMoreMenuItemTest = () => ( diff --git a/docs/reference-guides/slotfills/plugin-post-publish-panel.md b/docs/reference-guides/slotfills/plugin-post-publish-panel.md index 59972af3505ec..c7457c2eeb4eb 100644 --- a/docs/reference-guides/slotfills/plugin-post-publish-panel.md +++ b/docs/reference-guides/slotfills/plugin-post-publish-panel.md @@ -6,7 +6,7 @@ This slot allows for injecting items into the bottom of the post-publish panel t ```js import { registerPlugin } from '@wordpress/plugins'; -import { PluginPostPublishPanel } from '@wordpress/edit-post'; +import { PluginPostPublishPanel } from '@wordpress/editor'; const PluginPostPublishPanelTest = () => ( diff --git a/docs/reference-guides/slotfills/plugin-pre-publish-panel.md b/docs/reference-guides/slotfills/plugin-pre-publish-panel.md index d15e39bf09f4c..ac9fa13a882f4 100644 --- a/docs/reference-guides/slotfills/plugin-pre-publish-panel.md +++ b/docs/reference-guides/slotfills/plugin-pre-publish-panel.md @@ -6,7 +6,7 @@ This slot allows for injecting items into the bottom of the pre-publish panel th ```js import { registerPlugin } from '@wordpress/plugins'; -import { PluginPrePublishPanel } from '@wordpress/edit-post'; +import { PluginPrePublishPanel } from '@wordpress/editor'; const PluginPrePublishPanelTest = () => ( diff --git a/docs/reference-guides/theme-json-reference/README.md b/docs/reference-guides/theme-json-reference/README.md index 11605b21625ad..9f53a09a97e66 100644 --- a/docs/reference-guides/theme-json-reference/README.md +++ b/docs/reference-guides/theme-json-reference/README.md @@ -2,9 +2,10 @@ This reference guide lists the settings and style properties defined in the theme.json schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guide on how to use the theme.json file in your theme. -- [Version 2 (living reference)](/docs/reference-guides/theme-json-reference/theme-json-living.md) +- [Version 3 (latest)](/docs/reference-guides/theme-json-reference/theme-json-living.md) ## Older versions - [Migrating to Newer Theme.json Versions](/docs/reference-guides/theme-json-reference/theme-json-migrations.md) - [Version 1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) +- [Version 2](/docs/reference-guides/theme-json-reference/theme-json-v2.md) diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 0377f6a3c3e05..ea11c0a389883 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -1,27 +1,24 @@ -# Theme.json Version 2 +# Theme.json Version 3 Reference (latest) -> This is the living specification for **version 2** of `theme.json`. This version works with WordPress 5.9 or later, and the latest Gutenberg plugin. +> This is the living specification for **version 3** of `theme.json`. This version works with WordPress 6.6 or later, and the latest Gutenberg plugin. > > There are some related documents that you may be interested in: -> - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, and -> - the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). +> - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, +> - the [theme.json v2](/docs/reference-guides/theme-json-reference/theme-json-v2.md) specification, and +> - the [reference to migrate from older theme.json versions](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guidance on how to use the `theme.json` file in your theme. -## Schema +## JSON Schema -Remembering the `theme.json` settings and properties while you develop can be difficult, so a [JSON schema](https://schemas.wp.org/trunk/theme.json) was created to help. +This documentation was generated from the JSON schema for theme.json. -Code editors can pick up the schema and can provide helpful hints and suggestions such as tooltips, autocomplete, or schema validation in the editor. To use the schema in Visual Studio Code, add `$schema`: "https://schemas.wp.org/trunk/theme.json" to the beginning of your theme.json file together with a `version` corresponding to the version you wish to use, e.g.: +The latest schema for version 3, including all the latest changes from the Gutenberg plugin, is available at https://schemas.wp.org/trunk/theme.json. -``` -{ - "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, - ... -} -``` +Theme.json schemas for each WordPress version are available at https://schemas.wp.org/wp/{{version}}/theme.json. +For example, a schema for WordPress 5.8 is available at https://schemas.wp.org/wp/5.8/theme.json. +See [Developing with theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md#developing-with-themejson) for how to use the JSON schema in your editor. ## Settings @@ -58,7 +55,7 @@ Please note that when using this setting, `styles.spacing.padding` should always Settings related to borders. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | color | boolean | false | | | radius | boolean | false | | | style | boolean | false | | @@ -71,7 +68,7 @@ Settings related to borders. Settings related to shadows. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | defaultPresets | boolean | true | | | presets | array | | name, shadow, slug | @@ -82,7 +79,7 @@ Settings related to shadows. Settings related to colors. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | background | boolean | true | | | custom | boolean | true | | | customDuotone | boolean | true | | @@ -106,7 +103,7 @@ Settings related to colors. Settings related to background. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | backgroundImage | boolean | false | | | backgroundSize | boolean | false | | @@ -117,7 +114,7 @@ Settings related to background. Settings related to dimensions. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | aspectRatio | boolean | false | | | minHeight | boolean | false | | @@ -128,7 +125,7 @@ Settings related to dimensions. Settings related to layout. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | contentSize | string | | | | wideSize | string | | | | allowEditing | boolean | true | | @@ -141,7 +138,7 @@ Settings related to layout. Settings related to the lightbox. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | enabled | boolean | | | | allowEditing | boolean | | | @@ -152,7 +149,7 @@ Settings related to the lightbox. Settings related to position. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | sticky | boolean | false | | --- @@ -162,8 +159,8 @@ Settings related to position. Settings related to spacing. | Property | Type | Default | Props | -| --- | --- | --- |--- | -| blockGap | undefined | null | | +| --- | --- | --- |--- | +| blockGap | boolean, null | null | | | margin | boolean | false | | | padding | boolean | false | | | units | array | px,em,rem,vh,vw,% | | @@ -178,11 +175,12 @@ Settings related to spacing. Settings related to typography. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | +| defaultFontSizes | boolean | true | | | customFontSize | boolean | true | | | fontStyle | boolean | true | | | fontWeight | boolean | true | | -| fluid | undefined | false | | +| fluid | object, boolean | false | _{maxViewportWidth, minFontSize, minViewportWidth}_ | | letterSpacing | boolean | true | | | lineHeight | boolean | false | | | textAlign | boolean | true | | diff --git a/docs/reference-guides/theme-json-reference/theme-json-migrations.md b/docs/reference-guides/theme-json-reference/theme-json-migrations.md index b043ca1fba52a..c304bfe39493e 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-migrations.md +++ b/docs/reference-guides/theme-json-reference/theme-json-migrations.md @@ -1,4 +1,4 @@ -# Migrating to Newer Versions +# Migrating Theme.json to Newer Versions This guide documents the changes between different `theme.json` versions and how to upgrade. Using older versions will continue to be supported. Upgrading is recommended because new development will continue in the newer versions. @@ -6,7 +6,7 @@ This guide documents the changes between different `theme.json` versions and how Upgrading to v2 enables some new features and adjusts the naming of some old features to be more consistent with one another. -How to migrate from v1 to v2: +### How to migrate from v1 to v2: 1. Update `version` to `2`. 2. Rename the properties that were updated (see below) if you're using them. @@ -63,3 +63,33 @@ Additions to styles: ### Changes to property values The default font sizes provided by core (`settings.typography.fontSizes`) have been updated. The Normal and Huge sizes (with `normal` and `huge` slugs) have been removed from the list, and Extra Large (`x-large` slug) has been added. When the UI controls show the default values provided by core, Normal and Huge will no longer be present. However, their CSS classes and CSS Custom Properties are still enqueued to make sure existing content that uses them still works as expected. + +## Migrating from v2 to v3 + +Upgrading to v3 adjusts preset defaults to be more consistent with one another. + +### How to migrate from v2 to v3: + +1. Update `version` to `3`. +2. Configure the changed defaults (see below). + +### Changed defaults + +#### `settings.typography.defaultFontSizes` + +In theme.json v2, the default font sizes were only shown when theme sizes were not defined. A theme providing font sizes with the same slugs as the defaults would always override them. + +The default `fontSizes` slugs are: `small`, `medium`, `large`, `x-large`, and `xx-large`. + +The new `defaultFontSizes` option gives control over showing default font sizes and preventing those defaults from being overridden. + +- When set to `true` it will show the default font sizes and prevent them from being overridden by the theme. +- When set to `false` it will hide the default font sizes and allow the theme to use the default slugs. + +It is `true` by default when switching to v3. This is to be consistent with how other `default*` options work such as `settings.color.defaultPalette`, but differs from the behavior in v2. + +In theme.json v2, the default font sizes were only shown when theme sizes were not defined. A theme providing font sizes with the same slugs as the defaults would always override the default ones. + +To keep behavior similar to v2 with a v3 theme.json: +* If you do not have any `fontSizes` defined, `defaultFontSizes` can be left out or set to `true`. +* If you have some `fontSizes` defined, set `defaultFontSizes` to `false`. diff --git a/docs/reference-guides/theme-json-reference/theme-json-v1.md b/docs/reference-guides/theme-json-reference/theme-json-v1.md index 3e7096ee420ef..d7c98961b053a 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-v1.md +++ b/docs/reference-guides/theme-json-reference/theme-json-v1.md @@ -1,6 +1,24 @@ # Theme.json Version 1 Reference -Theme.json version 2 has been released, see the [theme.json migration guide](/docs/reference-guides/theme-json-reference/theme-json-migrations.md#migrating-from-v1-to-v2) for updating to the latest version. +> This is the specification for **version 1** of `theme.json`. This version works with WordPress 5.8 or later. + +
+ +Theme.json version 2 has been released with WordPress 5.9. WordPress will continue to support theme.json version 1. However new features will only be added to [new versions](/docs/reference-guides/theme-json-reference/theme-json-living.md). + +When you are ready to upgrade, see the [theme.json migration guide](/docs/reference-guides/theme-json-reference/theme-json-migrations.md#migrating-from-v1-to-v2) for details on updating to the latest version. + +
+ +This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guidance on how to use the `theme.json` file in your theme. + +## JSON Schema + +The last schema for version 1 is available at `https://schemas.wp.org/wp/5.8/theme.json`. + +Theme.json schemas for each WordPress version are available at `https://schemas.wp.org/wp/{{version}}/theme.json`. For example a schema for WordPress 5.8 is available at `https://schemas.wp.org/wp/5.8/theme.json`. + +See [Developing with theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md#developing-with-themejson) for how to use the JSON schema in your editor. ## Settings diff --git a/docs/reference-guides/theme-json-reference/theme-json-v2.md b/docs/reference-guides/theme-json-reference/theme-json-v2.md new file mode 100644 index 0000000000000..6c6e3b2894833 --- /dev/null +++ b/docs/reference-guides/theme-json-reference/theme-json-v2.md @@ -0,0 +1,330 @@ +# Theme.json Version 2 Reference + +> This is the specification for **version 2** of `theme.json`. This version works with WordPress 5.9 or later. + +
+ +Theme.json version 3 has been released with WordPress 6.6. WordPress will continue to support theme.json version 2. However new features will only be added to [new versions](/docs/reference-guides/theme-json-reference/theme-json-living.md). + +When you are ready to upgrade, see the [theme.json migration guide](/docs/reference-guides/theme-json-reference/theme-json-migrations.md#migrating-from-v2-to-v3) for details on updating to the latest version. + +
+ +This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/global-settings-and-styles.md) for examples and guidance on how to use the `theme.json` file in your theme. + +## JSON Schema + +This documentation was generated from the JSON schema for theme.json. + +The last schema for version 2 is available at `https://schemas.wp.org/wp/6.5/theme.json`. + +Theme.json schemas for each WordPress version are available at `https://schemas.wp.org/wp/{{version}}/theme.json`. For example a schema for WordPress 5.9 is available at `https://schemas.wp.org/wp/5.9/theme.json`. + +See [Developing with theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md#developing-with-themejson) for how to use the JSON schema in your editor. + +## Settings + +### appearanceTools + +Setting that enables the following UI tools: + +- background: backgroundImage, backgroundSize +- border: color, radius, style, width +- color: link +- dimensions: aspectRatio, minHeight +- position: sticky +- spacing: blockGap, margin, padding +- typography: lineHeight + +--- + +### useRootPaddingAwareAlignments + +Enables root padding (the values from `styles.spacing.padding`) to be applied to the contents of full-width blocks instead of the root block. + +Please note that when using this setting, `styles.spacing.padding` should always be set as an object with `top`, `right`, `bottom`, `left` values declared separately. + +--- + +### border + +Settings related to borders. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| color | boolean | false | | +| radius | boolean | false | | +| style | boolean | false | | +| width | boolean | false | | + +--- + +### shadow + +Settings related to shadows. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| defaultPresets | boolean | true | | +| presets | array | | name, shadow, slug | + +--- + +### color + +Settings related to colors. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| background | boolean | true | | +| custom | boolean | true | | +| customDuotone | boolean | true | | +| customGradient | boolean | true | | +| defaultDuotone | boolean | true | | +| defaultGradients | boolean | true | | +| defaultPalette | boolean | true | | +| duotone | array | | colors, name, slug | +| gradients | array | | gradient, name, slug | +| link | boolean | false | | +| palette | array | | color, name, slug | +| text | boolean | true | | +| heading | boolean | true | | +| button | boolean | true | | + +--- + +### background + +Settings related to background. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| backgroundImage | boolean | false | | + +--- + +### dimensions + +Settings related to dimensions. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| aspectRatio | boolean | false | | +| minHeight | boolean | false | | + +--- + +### layout + +Settings related to layout. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| contentSize | string | | | +| wideSize | string | | | +| allowEditing | boolean | true | | +| allowCustomContentAndWideSize | boolean | true | | + +--- + +### lightbox + +Settings related to the lightbox. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| enabled | boolean | | | +| allowEditing | boolean | | | + +--- + +### position + +Settings related to position. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| sticky | boolean | false | | + +--- + +### spacing + +Settings related to spacing. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| blockGap | boolean, null | null | | +| margin | boolean | false | | +| padding | boolean | false | | +| units | array | px,em,rem,vh,vw,% | | +| customSpacingSize | boolean | true | | +| spacingSizes | array | | name, size, slug | +| spacingScale | object | | | + +--- + +### typography + +Settings related to typography. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| customFontSize | boolean | true | | +| fontStyle | boolean | true | | +| fontWeight | boolean | true | | +| fluid | object, boolean | false | _{maxViewportWidth, minFontSize, minViewportWidth}_ | +| letterSpacing | boolean | true | | +| lineHeight | boolean | false | | +| textColumns | boolean | false | | +| textDecoration | boolean | true | | +| writingMode | boolean | false | | +| textTransform | boolean | true | | +| dropCap | boolean | true | | +| fontSizes | array | | fluid, name, size, slug | +| fontFamilies | array | | fontFace, fontFamily, name, slug | + +--- + +### custom + +Generate custom CSS custom properties of the form `--wp--custom--{key}--{nested-key}: {value};`. `camelCased` keys are transformed to `kebab-case` as to follow the CSS property naming schema. Keys at different depth levels are separated by `--`, so keys should not include `--` in the name. + +--- + +## Styles + +### border + +Border styles. + +| Property | Type | Props | +| --- | --- |--- | +| color | string, object | | +| radius | string, object | | +| style | string, object | | +| width | string, object | | +| top | object | color, style, width | +| right | object | color, style, width | +| bottom | object | color, style, width | +| left | object | color, style, width | + +--- + +### color + +Color styles. + +| Property | Type | Props | +| --- | --- |--- | +| background | string, object | | +| gradient | string, object | | +| text | string, object | | + +--- + +### dimensions + +Dimensions styles + +| Property | Type | Props | +| --- | --- |--- | +| aspectRatio | string, object | | +| minHeight | string, object | | + +--- + +### spacing + +Spacing styles. + +| Property | Type | Props | +| --- | --- |--- | +| blockGap | string, object | | +| margin | object | bottom, left, right, top | +| padding | object | bottom, left, right, top | + +--- + +### typography + +Typography styles. + +| Property | Type | Props | +| --- | --- |--- | +| fontFamily | string, object | | +| fontSize | string, object | | +| fontStyle | string, object | | +| fontWeight | string, object | | +| letterSpacing | string, object | | +| lineHeight | string, object | | +| textColumns | string | | +| textDecoration | string, object | | +| writingMode | string, object | | +| textTransform | string, object | | + +--- + +### filter + +CSS and SVG filter styles. + +| Property | Type | Props | +| --- | --- |--- | +| duotone | string, object | | + +--- + +### shadow + +Box shadow styles. + +--- + +### outline + +Outline styles. + +| Property | Type | Props | +| --- | --- |--- | +| color | string, object | | +| offset | string, object | | +| style | string, object | | +| width | string, object | | + +--- + +### css + +Sets custom CSS to apply styling not covered by other theme.json properties. + +--- + +## customTemplates + +Additional metadata for custom templates defined in the templates folder. + +Type: `object`. + +| Property | Description | Type | +| --- | --- | --- | +| name | Filename, without extension, of the template in the templates folder. | string | +| title | Title of the template, translatable. | string | +| postTypes | List of post types that can use this custom template. | array | + +## templateParts + +Additional metadata for template parts defined in the parts folder. + +Type: `object`. + +| Property | Description | Type | +| --- | --- | --- | +| name | Filename, without extension, of the template in the parts folder. | string | +| title | Title of the template, translatable. | string | +| area | The area the template part is used for. Block variations for `header` and `footer` values exist and will be used when the area is set to one of those. | string | + +## Patterns + +An array of pattern slugs to be registered from the Pattern Directory. +Type: `array`. diff --git a/docs/toc.json b/docs/toc.json index 94dea966cd3e0..fa80ee6c4f440 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -202,6 +202,22 @@ } ] }, + { + "docs/reference-guides/interactivity-api/README.md": [ + { + "docs/reference-guides/interactivity-api/iapi-quick-start-guide.md": [] + }, + { + "docs/reference-guides/interactivity-api/api-reference.md": [] + }, + { + "docs/reference-guides/interactivity-api/iapi-about.md": [] + }, + { + "docs/reference-guides/interactivity-api/iapi-faq.md": [] + } + ] + }, { "docs/reference-guides/slotfills/README.md": [ { @@ -240,6 +256,9 @@ { "docs/reference-guides/theme-json-reference/theme-json-v1.md": [] }, + { + "docs/reference-guides/theme-json-reference/theme-json-v2.md": [] + }, { "docs/reference-guides/theme-json-reference/theme-json-migrations.md": [] }, diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index 2b799aab7983a..e830012bc5770 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -1,3 +1,5 @@ +/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ + /** * External dependencies */ @@ -124,6 +126,29 @@ function generateRootManifestFromTOCItems( items, parent = null ) { pageItems = pageItems.concat( getPackageManifest( packagePaths ) ); } } ); + + const slugs = pageItems.map( ( { slug } ) => slug ); + const duplicatedSlugs = slugs.filter( + ( item, idx ) => idx !== slugs.indexOf( item ) + ); + + const FgRed = '\x1b[31m'; + const Reset = '\x1b[0m'; + + if ( duplicatedSlugs.length > 0 ) { + console.error( + `${ FgRed } The handbook generation setup creates pages based on their slug, so each slug has to be unique. ${ Reset }` + ); + console.error( + `${ FgRed } More info at https://github.com/WordPress/gutenberg/issues/61206#issuecomment-2085361154 ${ Reset }\n` + ); + throw new Error( + `${ FgRed } Duplicate slugs found in the TOC: ${ duplicatedSlugs.join( + ', ' + ) } ${ Reset }` + ); + } + return pageItems; } diff --git a/gutenberg.php b/gutenberg.php index 7063a0fb294a6..dc7a76c1531ba 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,9 +3,9 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. - * Requires at least: 6.3 - * Requires PHP: 7.0 - * Version: 18.1.2 + * Requires at least: 6.4 + * Requires PHP: 7.2 + * Version: 18.3.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php index 7095789722959..8e3c06159a120 100644 --- a/lib/block-supports/background.php +++ b/lib/block-supports/background.php @@ -53,10 +53,10 @@ function gutenberg_render_background_support( $block_content, $block ) { } $background_styles = array(); - $background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; $background_styles['backgroundImage'] = isset( $block_attributes['style']['background']['backgroundImage'] ) ? $block_attributes['style']['background']['backgroundImage'] : array(); - if ( isset( $background_styles['backgroundImage']['source'] ) && 'file' === $background_styles['backgroundImage']['source'] && isset( $background_styles['backgroundImage']['url'] ) ) { + if ( ! empty( $background_styles['backgroundImage'] ) ) { + $background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; $background_styles['backgroundPosition'] = isset( $block_attributes['style']['background']['backgroundPosition'] ) ? $block_attributes['style']['background']['backgroundPosition'] : null; $background_styles['backgroundRepeat'] = isset( $block_attributes['style']['background']['backgroundRepeat'] ) ? $block_attributes['style']['background']['backgroundRepeat'] : null; diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index eefab9ceffbaf..35a41270a1980 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -5,6 +5,19 @@ * @package gutenberg */ +/** + * Update the block content with elements class names. + * + * @deprecated 6.6.0 Use `gutenberg_render_elements_class_name` instead. + * + * @param string $block_content Rendered block content. + * @return string Filtered block content. + */ +function gutenberg_render_elements_support( $block_content ) { + _deprecated_function( __FUNCTION__, '6.6.0', 'gutenberg_render_elements_class_name' ); + return $block_content; +} + /** * Determines whether an elements class name should be added to the block. * @@ -86,11 +99,31 @@ function gutenberg_should_add_elements_class_name( $block, $options ) { * This solves the issue of an element (e.g.: link color) being styled in both the parent and a descendant: * we want the descendant style to take priority, and this is done by loading it after, in DOM order. * + * @since 6.6.0 Element block support class and styles are generated via the `render_block_data` filter instead of `pre_render_block` + * * @param array $parsed_block The parsed block. * * @return array The same parsed block with elements classname added if appropriate. */ function gutenberg_render_elements_support_styles( $parsed_block ) { + /* + * The generation of element styles and classname were moved to the + * `render_block_data` filter in 6.6.0 to avoid filtered attributes + * breaking the application of the elements CSS class. + * + * @see https://github.com/WordPress/gutenberg/pull/59535. + * + * The change in filter means, the argument types for this function + * have changed and require deprecating. + */ + if ( is_string( $parsed_block ) ) { + _deprecated_argument( + __FUNCTION__, + '6.6.0', + __( 'Use as a `pre_render_block` filter is deprecated. Use with `render_block_data` instead.', 'gutenberg' ) + ); + } + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); $element_block_styles = $parsed_block['attrs']['style']['elements'] ?? null; @@ -193,8 +226,8 @@ function gutenberg_render_elements_support_styles( $parsed_block ) { } /** - * Ensure the elements block support class name generated and added to - * block attributes in the `render_block_data` filter gets applied to the + * Ensure the elements block support class name generated, and added to + * block attributes, in the `render_block_data` filter gets applied to the * block's markup. * * @see gutenberg_render_elements_support_styles @@ -215,17 +248,19 @@ function gutenberg_render_elements_class_name( $block_content, $block ) { $tags = new WP_HTML_Tag_Processor( $block_content ); if ( $tags->next_tag() ) { - // Ensure the elements class name set in render_block_data filter is applied in markup. - // See `gutenberg_render_elements_support_styles`. $tags->add_class( $matches[0] ); } return $tags->get_updated_html(); } -// Remove WordPress core filters to avoid rendering duplicate elements stylesheet & attaching classes twice. +// Remove deprecated WordPress core filters. remove_filter( 'render_block', 'wp_render_elements_support', 10, 2 ); remove_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10, 2 ); + +// Remove WordPress core filters to avoid rendering duplicate elements stylesheet & attaching classes twice. remove_filter( 'render_block', 'wp_render_elements_class_name', 10, 2 ); +remove_filter( 'render_block_data', 'wp_render_elements_support_styles', 10, 1 ); + add_filter( 'render_block', 'gutenberg_render_elements_class_name', 10, 2 ); -add_filter( 'render_block_data', 'gutenberg_render_elements_support_styles', 10, 2 ); +add_filter( 'render_block_data', 'gutenberg_render_elements_support_styles', 10, 1 ); diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 325505ff60359..aac62e402148a 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -573,112 +573,116 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { return $block_content; } - $outer_class_names = array(); - $container_content_class = wp_unique_id( 'wp-container-content-' ); - $child_layout_declarations = array(); - $child_layout_styles = array(); - - $self_stretch = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null; - - if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) { - $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize']; - $child_layout_declarations['box-sizing'] = 'border-box'; - } elseif ( 'fill' === $self_stretch ) { - $child_layout_declarations['flex-grow'] = '1'; - } + $outer_class_names = array(); - $column_start = isset( $block['attrs']['style']['layout']['columnStart'] ) ? $block['attrs']['style']['layout']['columnStart'] : null; - $column_span = isset( $block['attrs']['style']['layout']['columnSpan'] ) ? $block['attrs']['style']['layout']['columnSpan'] : null; - if ( $column_start && $column_span ) { - $child_layout_declarations['grid-column'] = "$column_start / span $column_span"; - } elseif ( $column_start ) { - $child_layout_declarations['grid-column'] = "$column_start"; - } elseif ( $column_span ) { - $child_layout_declarations['grid-column'] = "span $column_span"; - } + // Child layout specific logic. + if ( $child_layout ) { + $container_content_class = wp_unique_id( 'wp-container-content-' ); + $child_layout_declarations = array(); + $child_layout_styles = array(); - $row_start = isset( $block['attrs']['style']['layout']['rowStart'] ) ? $block['attrs']['style']['layout']['rowStart'] : null; - $row_span = isset( $block['attrs']['style']['layout']['rowSpan'] ) ? $block['attrs']['style']['layout']['rowSpan'] : null; - if ( $row_start && $row_span ) { - $child_layout_declarations['grid-row'] = "$row_start / span $row_span"; - } elseif ( $row_start ) { - $child_layout_declarations['grid-row'] = "$row_start"; - } elseif ( $row_span ) { - $child_layout_declarations['grid-row'] = "span $row_span"; - } + $self_stretch = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null; - $child_layout_styles[] = array( - 'selector' => ".$container_content_class", - 'declarations' => $child_layout_declarations, - ); + if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) { + $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize']; + $child_layout_declarations['box-sizing'] = 'border-box'; + } elseif ( 'fill' === $self_stretch ) { + $child_layout_declarations['flex-grow'] = '1'; + } - $minimum_column_width = isset( $block['attrs']['style']['layout']['minimumColumnWidth'] ) ? $block['attrs']['style']['layout']['minimumColumnWidth'] : null; - $column_count = isset( $block['attrs']['style']['layout']['columnCount'] ) ? $block['attrs']['style']['layout']['columnCount'] : null; + $column_start = isset( $block['attrs']['style']['layout']['columnStart'] ) ? $block['attrs']['style']['layout']['columnStart'] : null; + $column_span = isset( $block['attrs']['style']['layout']['columnSpan'] ) ? $block['attrs']['style']['layout']['columnSpan'] : null; + if ( $column_start && $column_span ) { + $child_layout_declarations['grid-column'] = "$column_start / span $column_span"; + } elseif ( $column_start ) { + $child_layout_declarations['grid-column'] = "$column_start"; + } elseif ( $column_span ) { + $child_layout_declarations['grid-column'] = "span $column_span"; + } - /* - * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set, - * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed - * once the grid has less columns than the start. - * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set. - * In that case, if columnCount doesn't exist, we can assume that the grid is responsive. - */ - if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) { - $column_span_number = floatval( $column_span ); - $column_start_number = floatval( $column_start ); - $highest_number = max( $column_span_number, $column_start_number ); - $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem'; - $parent_column_value = floatval( $parent_column_width ); - $parent_column_unit = explode( $parent_column_value, $parent_column_width ); + $row_start = isset( $block['attrs']['style']['layout']['rowStart'] ) ? $block['attrs']['style']['layout']['rowStart'] : null; + $row_span = isset( $block['attrs']['style']['layout']['rowSpan'] ) ? $block['attrs']['style']['layout']['rowSpan'] : null; + if ( $row_start && $row_span ) { + $child_layout_declarations['grid-row'] = "$row_start / span $row_span"; + } elseif ( $row_start ) { + $child_layout_declarations['grid-row'] = "$row_start"; + } elseif ( $row_span ) { + $child_layout_declarations['grid-row'] = "span $row_span"; + } + + $child_layout_styles[] = array( + 'selector' => ".$container_content_class", + 'declarations' => $child_layout_declarations, + ); + + $minimum_column_width = isset( $block['parentLayout']['minimumColumnWidth'] ) ? $block['parentLayout']['minimumColumnWidth'] : null; + $column_count = isset( $block['parentLayout']['columnCount'] ) ? $block['parentLayout']['columnCount'] : null; /* - * If there is no unit, the width has somehow been mangled so we reset both unit and value - * to defaults. - * Additionally, the unit should be one of px, rem or em, so that also needs to be checked. + * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set, + * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed + * once the grid has less columns than the start. + * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set. + * In that case, if columnCount doesn't exist, we can assume that the grid is responsive. */ - if ( count( $parent_column_unit ) <= 1 ) { - $parent_column_unit = 'rem'; - $parent_column_value = 12; - } else { - $parent_column_unit = $parent_column_unit[1]; + if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) { + $column_span_number = floatval( $column_span ); + $column_start_number = floatval( $column_start ); + $highest_number = max( $column_span_number, $column_start_number ); + $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem'; + $parent_column_value = floatval( $parent_column_width ); + $parent_column_unit = explode( $parent_column_value, $parent_column_width ); + + /* + * If there is no unit, the width has somehow been mangled so we reset both unit and value + * to defaults. + * Additionally, the unit should be one of px, rem or em, so that also needs to be checked. + */ + if ( count( $parent_column_unit ) <= 1 ) { + $parent_column_unit = 'rem'; + $parent_column_value = 12; + } else { + $parent_column_unit = $parent_column_unit[1]; - if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) { - $parent_column_unit = 'rem'; + if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) { + $parent_column_unit = 'rem'; + } } + + /* + * A default gap value is used for this computation because custom gap values may not be + * viable to use in the computation of the container query value. + */ + $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5; + $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value; + $container_query_value = $container_query_value . $parent_column_unit; + // If a span is set we want to preserve it as long as possible, otherwise we just reset the value. + $grid_column_value = $column_span ? '1/-1' : 'auto'; + + $child_layout_styles[] = array( + 'rules_group' => "@container (max-width: $container_query_value )", + 'selector' => ".$container_content_class", + 'declarations' => array( + 'grid-column' => $grid_column_value, + ), + ); } /* - * A default gap value is used for this computation because custom gap values may not be - * viable to use in the computation of the container query value. + * Add to the style engine store to enqueue and render layout styles. + * Return styles here just to check if any exist. */ - $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5; - $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value; - $container_query_value = $container_query_value . $parent_column_unit; - // If a span is set we want to preserve it as long as possible, otherwise we just reset the value. - $grid_column_value = $column_span ? '1/-1' : 'auto'; - - $child_layout_styles[] = array( - 'rules_group' => "@container (max-width: $container_query_value )", - 'selector' => ".$container_content_class", - 'declarations' => array( - 'grid-column' => $grid_column_value, - ), + $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules( + $child_layout_styles, + array( + 'context' => 'block-supports', + 'prettify' => false, + ) ); - } - /* - * Add to the style engine store to enqueue and render layout styles. - * Return styles here just to check if any exist. - */ - $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules( - $child_layout_styles, - array( - 'context' => 'block-supports', - 'prettify' => false, - ) - ); - - if ( $child_css ) { - $outer_class_names[] = $container_content_class; + if ( $child_css ) { + $outer_class_names[] = $container_content_class; + } } // Prep the processor for modifying the block output. diff --git a/lib/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php index 6877a209b687f..c564016b1a711 100644 --- a/lib/class-wp-theme-json-data-gutenberg.php +++ b/lib/class-wp-theme-json-data-gutenberg.php @@ -38,7 +38,7 @@ class WP_Theme_JSON_Data_Gutenberg { * @param array $data Array following the theme.json specification. * @param string $origin The origin of the data: default, theme, user. */ - public function __construct( $data = array(), $origin = 'theme' ) { + public function __construct( $data = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) { $this->origin = $origin; $this->theme_json = new WP_Theme_JSON_Gutenberg( $data, $this->origin ); } diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1d3f8feb90e23..77004253bea86 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -159,7 +159,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => false, + 'prevent_override' => array( 'typography', 'defaultFontSizes' ), 'use_default_names' => true, 'value_func' => 'gutenberg_get_typography_font_size_value', 'css_vars' => '--wp--preset--font-size--$slug', @@ -428,20 +428,21 @@ class WP_Theme_JSON_Gutenberg { 'defaultPresets' => null, ), 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textAlign' => null, - 'textColumns' => null, - 'textDecoration' => null, - 'textTransform' => null, - 'writingMode' => null, + 'fluid' => null, + 'customFontSize' => null, + 'defaultFontSizes' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textAlign' => null, + 'textColumns' => null, + 'textDecoration' => null, + 'textTransform' => null, + 'writingMode' => null, ), ); @@ -705,9 +706,10 @@ public static function get_element_class_name( $element ) { * * @since 5.8.0 * @since 5.9.0 Changed value from 1 to 2. + * @since 6.5.0 Changed value from 2 to 3. * @var int */ - const LATEST_SCHEMA = 2; + const LATEST_SCHEMA = 3; /** * Constructor. @@ -718,7 +720,7 @@ public static function get_element_class_name( $element ) { * @param string $origin Optional. What source of data this object represents. * One of 'default', 'theme', or 'custom'. Default 'theme'. */ - public function __construct( $theme_json = array(), $origin = 'theme' ) { + public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } @@ -727,15 +729,9 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { $registry = WP_Block_Type_Registry::get_instance(); $valid_block_names = array_keys( $registry->get_all_registered() ); $valid_element_names = array_keys( static::ELEMENTS ); - $valid_variations = array(); - foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { - if ( ! isset( $block_meta['styleVariations'] ) ) { - continue; - } - $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); - } - $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); - $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); + $valid_variations = static::get_valid_block_style_variations(); + $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); + $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); // Internally, presets are keyed by origin. $nodes = static::get_setting_nodes( $this->theme_json ); @@ -1200,7 +1196,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); } foreach ( $style_nodes as &$node ) { - $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); + $node = static::scope_style_node_selectors( $options['scope'], $node ); } unset( $node ); } @@ -1834,6 +1830,10 @@ protected static function compute_preset_classes( $settings, $selector, $origins * @return string Scoped selector. */ public static function scope_selector( $scope, $selector ) { + if ( ! $scope || ! $selector ) { + return $selector; + } + $scopes = explode( ',', $scope ); $selectors = explode( ',', $selector ); @@ -1856,6 +1856,39 @@ public static function scope_selector( $scope, $selector ) { return $result; } + /** + * Scopes the selectors for a given style node. This includes the primary + * selector, i.e. `$node['selector']`, as well as any custom selectors for + * features and subfeatures, e.g. `$node['selectors']['border']` etc. + * + * @since 6.6.0 + * + * @param string $scope Selector to scope to. + * @param array $node Style node with selectors to scope. + * + * @return array Node with updated selectors. + */ + protected static function scope_style_node_selectors( $scope, $node ) { + $node['selector'] = static::scope_selector( $scope, $node['selector'] ); + + if ( empty( $node['selectors'] ) ) { + return $node; + } + + foreach ( $node['selectors'] as $feature => $selector ) { + if ( is_string( $selector ) ) { + $node['selectors'][ $feature ] = static::scope_selector( $scope, $selector ); + } + if ( is_array( $selector ) ) { + foreach ( $selector as $subfeature => $subfeature_selector ) { + $node['selectors'][ $feature ][ $subfeature ] = static::scope_selector( $scope, $subfeature_selector ); + } + } + } + + return $node; + } + /** * Gets preset values keyed by slugs based on settings and metadata. * @@ -2112,8 +2145,8 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * @since 5.8.0 * @since 5.9.0 Added the `$settings` and `$properties` parameters. * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters. - * @since 6.5.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). - * @since 6.6.0 Using style engine to correctly fetch background CSS values. + * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set. + * @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). Using style engine to correctly fetch background CSS values. * * @param array $styles Styles to process. * @param array $settings Theme settings. @@ -2765,7 +2798,7 @@ public function get_root_layout_rules( $selector, $block_metadata ) { * user-generated values take precedence in the CSS cascade. * @link https://github.com/WordPress/gutenberg/issues/36147. */ - $css .= 'body { margin: 0; }'; + $css .= ':where(body) { margin: 0; }'; if ( $use_root_padding ) { // Top and bottom padding are applied to the outer block container. @@ -2890,12 +2923,15 @@ public function merge( $incoming ) { } // Replace the presets. - foreach ( static::PRESETS_METADATA as $preset ) { - $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $prevent_override = $preset_metadata['prevent_override']; + if ( is_array( $prevent_override ) ) { + $prevent_override = _wp_array_get( $this->theme_json['settings'], $preset_metadata['prevent_override'] ); + } foreach ( static::VALID_ORIGINS as $origin ) { $base_path = $node['path']; - foreach ( $preset['path'] as $leaf ) { + foreach ( $preset_metadata['path'] as $leaf ) { $base_path[] = $leaf; } @@ -2907,7 +2943,8 @@ public function merge( $incoming ) { continue; } - if ( 'theme' === $origin && $preset['use_default_names'] ) { + // Set names for theme presets based on the slug if they are not set and can use default names. + if ( 'theme' === $origin && $preset_metadata['use_default_names'] ) { foreach ( $content as $key => $item ) { if ( ! isset( $item['name'] ) ) { $name = static::get_name_from_defaults( $item['slug'], $base_path ); @@ -2918,19 +2955,17 @@ public function merge( $incoming ) { } } - if ( - ( 'theme' !== $origin ) || - ( 'theme' === $origin && $override_preset ) - ) { - _wp_array_set( $this->theme_json, $path, $content ); - } else { - $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); - $slugs = array_merge_recursive( $slugs_global, $slugs_node ); + // Filter out default slugs from theme presets when defaults should not be overridden. + if ( 'theme' === $origin && $prevent_override ) { + $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); + $preset_global = _wp_array_get( $slugs_global, $preset_metadata['path'], array() ); + $preset_node = _wp_array_get( $slugs_node, $preset_metadata['path'], array() ); + $preset_slugs = array_merge_recursive( $preset_global, $preset_node ); - $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); - $content = static::filter_slugs( $content, $slugs_for_preset ); - _wp_array_set( $this->theme_json, $path, $content ); + $content = static::filter_slugs( $content, $preset_slugs ); } + + _wp_array_set( $this->theme_json, $path, $content ); } } } @@ -3126,13 +3161,7 @@ public static function remove_insecure_properties( $theme_json ) { $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); - $valid_variations = array(); - foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { - if ( ! isset( $block_meta['styleVariations'] ) ) { - continue; - } - $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); - } + $valid_variations = static::get_valid_block_style_variations(); $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations ); @@ -4039,4 +4068,21 @@ function ( $matches ) use ( $variation_class ) { return implode( ',', $result ); } + + /** + * Collects valid block style variations keyed by block type. + * + * @return array Valid block style variations by block type. + */ + protected static function get_valid_block_style_variations() { + $valid_variations = array(); + foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { + if ( ! isset( $block_meta['styleVariations'] ) ) { + continue; + } + $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); + } + + return $valid_variations; + } } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 472e671ee8a26..dcc0bf8b099c3 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -244,7 +244,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_json_data = static::read_json_file( $theme_json_file ); $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); } else { - $theme_json_data = array(); + $theme_json_data = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ); } /** @@ -375,7 +375,7 @@ public static function get_block_data() { return static::$blocks; } - $config = array( 'version' => 2 ); + $config = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ); foreach ( $blocks as $block_name => $block_type ) { if ( isset( $block_type->supports['__experimentalStyle'] ) ) { $config['styles']['blocks'][ $block_name ] = static::remove_json_comments( $block_type->supports['__experimentalStyle'] ); @@ -544,14 +544,17 @@ public static function get_user_data() { isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && $decoded_data['isGlobalStylesUserThemeJSON'] ) { - unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); $config = $decoded_data; } } /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ - $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); - $config = $theme_json->get_data(); + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); + $config = $theme_json->get_data(); + + // Needs to be set for schema migrations of user data. + $config['isGlobalStylesUserThemeJSON'] = true; + static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' ); return static::$user; diff --git a/lib/class-wp-theme-json-schema-gutenberg.php b/lib/class-wp-theme-json-schema-gutenberg.php index 8373e133c5aec..1eea7ddaa2736 100644 --- a/lib/class-wp-theme-json-schema-gutenberg.php +++ b/lib/class-wp-theme-json-schema-gutenberg.php @@ -38,6 +38,7 @@ class WP_Theme_JSON_Schema_Gutenberg { * Function that migrates a given theme.json structure to the last version. * * @since 5.9.0 + * @since 6.6.0 Migrate up to v3. * * @param array $theme_json The structure to migrate. * @@ -50,8 +51,14 @@ public static function migrate( $theme_json ) { ); } - if ( 1 === $theme_json['version'] ) { - $theme_json = self::migrate_v1_to_v2( $theme_json ); + // Migrate each version in order starting with the current version. + switch ( $theme_json['version'] ) { + case 1: + $theme_json = self::migrate_v1_to_v2( $theme_json ); + // no break + case 2: + $theme_json = self::migrate_v2_to_v3( $theme_json ); + // no break } return $theme_json; @@ -87,6 +94,56 @@ private static function migrate_v1_to_v2( $old ) { return $new; } + /** + * Migrates from v2 to v3. + * + * - Sets settings.typography.defaultFontSizes to false. + * + * @since 6.6.0 + * + * @param array $old Data to migrate. + * + * @return array Data with defaultFontSizes set to false. + */ + private static function migrate_v2_to_v3( $old ) { + // Copy everything. + $new = $old; + + // Set the new version. + $new['version'] = 3; + + /* + * Remaining changes do not need to be applied to the custom origin, + * as they should take on the value of the theme origin. + */ + if ( + isset( $new['isGlobalStylesUserThemeJSON'] ) && + true === $new['isGlobalStylesUserThemeJSON'] + ) { + return $new; + } + + /* + * Even though defaultFontSizes is a new setting, we need to migrate + * it as it controls the PRESETS_METADATA prevent_override which was + * previously hardcoded to false. This only needs to happen when the + * theme provided font sizes as they could match the default ones and + * affect the generated CSS. And in v2 we provided default font sizes + * when the theme did not provide any. + */ + if ( isset( $new['settings']['typography']['fontSizes'] ) ) { + if ( ! isset( $new['settings'] ) ) { + $new['settings'] = array(); + } + if ( ! isset( $new['settings']['typography'] ) ) { + $new['settings']['typography'] = array(); + } + $new['settings']['typography']['defaultFontSizes'] = false; + } + + return $new; + } + /** * Processes the settings subtree. * diff --git a/lib/compat/wordpress-6.4/block-hooks.php b/lib/compat/wordpress-6.4/block-hooks.php index 46115d5b6c629..f77582caf1345 100644 --- a/lib/compat/wordpress-6.4/block-hooks.php +++ b/lib/compat/wordpress-6.4/block-hooks.php @@ -290,7 +290,7 @@ function gutenberg_register_block_hooks_rest_field() { 'block_hooks', array( 'schema' => array( - 'description' => __( 'This block is automatically inserted near any occurence of the block types used as keys of this map, into a relative position given by the corresponding value.', 'gutenberg' ), + 'description' => __( 'This block is automatically inserted near any occurrence of the block types used as keys of this map, into a relative position given by the corresponding value.', 'gutenberg' ), 'type' => 'object', 'patternProperties' => array( '^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$' => array( diff --git a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php index 7f04820050a91..8eb6f5271adf6 100644 --- a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php +++ b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php @@ -12,7 +12,7 @@ /** * Class used for interacting with block bindings sources. * - * @since 6.5.0 + * @since 6.5.0 */ if ( ! class_exists( 'WP_Block_Bindings_Registry' ) ) { final class WP_Block_Bindings_Registry { diff --git a/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php b/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php index 4a445cd8ae3f7..d794a2601f45d 100644 --- a/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php +++ b/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php @@ -162,7 +162,7 @@ function ( $elem ) { * The schema structure should mirror the data tree. Each value provided in the * schema should be a callable that will be applied to sanitize the corresponding * value in the data tree. Keys that are in the data tree, but not present in the - * schema, will be removed in the santized data. Nested arrays are traversed recursively. + * schema, will be removed in the sanitized data. Nested arrays are traversed recursively. * * @since 6.5.0 * diff --git a/lib/compat/wordpress-6.6/block-template-utils.php b/lib/compat/wordpress-6.6/block-template-utils.php index 8dd35903af079..953f6bf20c077 100644 --- a/lib/compat/wordpress-6.6/block-template-utils.php +++ b/lib/compat/wordpress-6.6/block-template-utils.php @@ -111,3 +111,252 @@ function gutenberg_get_template_hierarchy( $slug, $is_custom = false, $template_ } return $template_hierarchy; } + +/** + * Retrieves the template files from the theme. + * + * @since 5.9.0 + * @since 6.3.0 Added the `$query` parameter. + * @access private + * + * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. + * @param array $query { + * Arguments to retrieve templates. Optional, empty by default. + * + * @type string[] $slug__in List of slugs to include. + * @type string[] $slug__not_in List of slugs to skip. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only). + * @type string $post_type Post type to get the templates for. + * } + * + * @return array Template + */ +function _gutenberg_get_block_templates_files( $template_type, $query = array() ) { + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + return null; + } + + // @core-merge: This code will go into Core's '_get_block_templates_files' function. + $default_template_types = array(); + if ( 'wp_template' === $template_type ) { + $default_template_types = get_default_block_template_types(); + } + // @core-merge: End of the code that will go into Core. + + // Prepare metadata from $query. + $slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); + $slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array(); + $area = isset( $query['area'] ) ? $query['area'] : null; + $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; + + $stylesheet = get_stylesheet(); + $template = get_template(); + $themes = array( + $stylesheet => get_stylesheet_directory(), + ); + // Add the parent theme if it's not the same as the current theme. + if ( $stylesheet !== $template ) { + $themes[ $template ] = get_template_directory(); + } + $template_files = array(); + foreach ( $themes as $theme_slug => $theme_dir ) { + $template_base_paths = get_block_theme_folders( $theme_slug ); + $theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] ); + foreach ( $theme_template_files as $template_file ) { + $template_base_path = $template_base_paths[ $template_type ]; + $template_slug = substr( + $template_file, + // Starting position of slug. + strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ), + // Subtract ending '.html'. + -5 + ); + + // Skip this item if its slug doesn't match any of the slugs to include. + if ( ! empty( $slugs_to_include ) && ! in_array( $template_slug, $slugs_to_include, true ) ) { + continue; + } + + // Skip this item if its slug matches any of the slugs to skip. + if ( ! empty( $slugs_to_skip ) && in_array( $template_slug, $slugs_to_skip, true ) ) { + continue; + } + + /* + * The child theme items (stylesheet) are processed before the parent theme's (template). + * If a child theme defines a template, prevent the parent template from being added to the list as well. + */ + if ( isset( $template_files[ $template_slug ] ) ) { + continue; + } + + $new_template_item = array( + 'slug' => $template_slug, + 'path' => $template_file, + 'theme' => $theme_slug, + 'type' => $template_type, + ); + + if ( 'wp_template_part' === $template_type ) { + $candidate = _add_block_template_part_area_info( $new_template_item ); + if ( ! isset( $area ) || ( isset( $area ) && $area === $candidate['area'] ) ) { + $template_files[ $template_slug ] = $candidate; + } + } + + if ( 'wp_template' === $template_type ) { + $candidate = _add_block_template_info( $new_template_item ); + $is_custom = ! isset( $default_template_types[ $candidate['slug'] ] ); + + if ( + ! $post_type || + ( $post_type && isset( $candidate['postTypes'] ) && in_array( $post_type, $candidate['postTypes'], true ) ) + ) { + $template_files[ $template_slug ] = $candidate; + } + + // @core-merge: This code will go into Core's '_get_block_templates_files' function. + // The custom templates with no associated post-types are available for all post-types. + if ( $post_type && ! isset( $candidate['postTypes'] ) && $is_custom ) { + $template_files[ $template_slug ] = $candidate; + } + // @core-merge: End of the code that will go into Core. + } + } + } + + return array_values( $template_files ); +} + +/** + * Retrieves a list of unified template objects based on a query. + * + * @since 5.8.0 + * + * @param array $query { + * Optional. Arguments to retrieve templates. + * + * @type string[] $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only). + * @type string $post_type Post type to get the templates for. + * } + * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. + * @return WP_Block_Template[] Array of block templates. + */ +function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) { + /** + * Filters the block templates array before the query takes place. + * + * Return a non-null value to bypass the WordPress queries. + * + * @since 5.9.0 + * + * @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query, + * or null to allow WP to run its normal queries. + * @param array $query { + * Arguments to retrieve templates. All arguments are optional. + * + * @type string[] $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only). + * @type string $post_type Post type to get the templates for. + * } + * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. + */ + $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type ); + if ( ! is_null( $templates ) ) { + return $templates; + } + + $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; + $wp_query_args = array( + 'post_status' => array( 'auto-draft', 'draft', 'publish' ), + 'post_type' => $template_type, + 'posts_per_page' => -1, + 'no_found_rows' => true, + 'lazy_load_term_meta' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'name', + 'terms' => get_stylesheet(), + ), + ), + ); + + if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'wp_template_part_area', + 'field' => 'name', + 'terms' => $query['area'], + ); + $wp_query_args['tax_query']['relation'] = 'AND'; + } + + if ( ! empty( $query['slug__in'] ) ) { + $wp_query_args['post_name__in'] = $query['slug__in']; + $wp_query_args['posts_per_page'] = count( array_unique( $query['slug__in'] ) ); + } + + // This is only needed for the regular templates/template parts post type listing and editor. + if ( isset( $query['wp_id'] ) ) { + $wp_query_args['p'] = $query['wp_id']; + } else { + $wp_query_args['post_status'] = 'publish'; + } + + $template_query = new WP_Query( $wp_query_args ); + $query_result = array(); + foreach ( $template_query->posts as $post ) { + $template = _build_block_template_result_from_post( $post ); + + if ( is_wp_error( $template ) ) { + continue; + } + + if ( $post_type && ! $template->is_custom ) { + continue; + } + + if ( + $post_type && + isset( $template->post_types ) && + ! in_array( $post_type, $template->post_types, true ) + ) { + continue; + } + + $query_result[] = $template; + } + + if ( ! isset( $query['wp_id'] ) ) { + /* + * If the query has found some use templates, those have priority + * over the theme-provided ones, so we skip querying and building them. + */ + $query['slug__not_in'] = wp_list_pluck( $query_result, 'slug' ); + $template_files = _gutenberg_get_block_templates_files( $template_type, $query ); + foreach ( $template_files as $template_file ) { + $query_result[] = _build_block_template_result_from_file( $template_file, $template_type ); + } + } + + /** + * Filters the array of queried block templates array after they've been fetched. + * + * @since 5.9.0 + * + * @param WP_Block_Template[] $query_result Array of found block templates. + * @param array $query { + * Arguments to retrieve templates. All arguments are optional. + * + * @type string[] $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only). + * @type string $post_type Post type to get the templates for. + * } + * @param string $template_type wp_template or wp_template_part. + */ + return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); +} diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php index 4a25e06608235..656e38ffe933f 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php @@ -17,7 +17,7 @@ class Gutenberg_REST_Templates_Controller_6_6 extends Gutenberg_REST_Templates_C /** * Checks if a given request has access to read templates. * - * @since 6.6 + * @since 6.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. @@ -41,10 +41,39 @@ public function get_items_permissions_check( $request ) { // phpcs:ignore Variab ); } + /** + * Returns a list of templates. + * + * @since 5.8.0 + * + * @param WP_REST_Request $request The request instance. + * @return WP_REST_Response + */ + public function get_items( $request ) { + $query = array(); + if ( isset( $request['wp_id'] ) ) { + $query['wp_id'] = $request['wp_id']; + } + if ( isset( $request['area'] ) ) { + $query['area'] = $request['area']; + } + if ( isset( $request['post_type'] ) ) { + $query['post_type'] = $request['post_type']; + } + + $templates = array(); + foreach ( gutenberg_get_block_templates( $query, $this->post_type ) as $template ) { + $data = $this->prepare_item_for_response( $template, $request ); + $templates[] = $this->prepare_response_for_collection( $data ); + } + + return rest_ensure_response( $templates ); + } + /** * Checks if a given request has access to read templates. * - * @since 6.6 + * @since 6.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. diff --git a/lib/compat/wordpress-6.6/compat.php b/lib/compat/wordpress-6.6/compat.php new file mode 100644 index 0000000000000..4e444d3149824 --- /dev/null +++ b/lib/compat/wordpress-6.6/compat.php @@ -0,0 +1,32 @@ += 6.6.0. + * + * @global array $submenu + */ +function gutenberg_change_patterns_link_and_remove_template_parts_submenu_item() { + if ( ! wp_is_block_theme() ) { + global $submenu; + + if ( empty( $submenu['themes.php'] ) ) { + return; + } + + foreach ( $submenu['themes.php'] as $key => $item ) { + if ( 'edit.php?post_type=wp_block' === $item[2] ) { + $submenu['themes.php'][ $key ][2] = 'site-editor.php?path=/patterns'; + } elseif ( 'site-editor.php?path=/wp_template_part/all' === $item[2] ) { + unset( $submenu['themes.php'][ $key ] ); + } + } + } +} +add_action( 'admin_init', 'gutenberg_change_patterns_link_and_remove_template_parts_submenu_item' ); diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php index c471be6b24031..9ec81467c261d 100644 --- a/lib/compat/wordpress-6.6/option.php +++ b/lib/compat/wordpress-6.6/option.php @@ -14,8 +14,6 @@ function gutenberg_update_initial_settings( $args, $defaults, $option_group, $op $settings_label_map = array( 'blogname' => __( 'Title' ), 'blogdescription' => __( 'Tagline' ), - 'site_logo' => __( 'Logo' ), - 'site_icon' => __( 'Icon' ), 'show_on_front' => __( 'Show on front' ), 'page_on_front' => __( 'Page on front' ), 'posts_per_page' => __( 'Maximum posts per page' ), diff --git a/lib/compat/wordpress-6.6/post.php b/lib/compat/wordpress-6.6/post.php new file mode 100644 index 0000000000000..8415f3bf42f18 --- /dev/null +++ b/lib/compat/wordpress-6.6/post.php @@ -0,0 +1,39 @@ +item_updated = __( 'Template updated.', 'gutenberg' ); + return $labels; +} +add_filter( 'post_type_labels_wp_template', 'gutenberg_update_wp_template_labels', 10, 1 ); + +/** + * Updates the labels for the template parts post type. + * + * @param object $labels Object with labels for the post type as member variables. + * @return object Object with all the labels as member variables. + */ +function gutenberg_update_wp_template__part_labels( $labels ) { + $labels->item_updated = __( 'Template part updated.', 'gutenberg' ); + return $labels; +} +add_filter( 'post_type_labels_wp_template_part', 'gutenberg_update_wp_template__part_labels', 10, 1 ); diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index bf462cd11ca4b..8526093dc99dd 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -29,3 +29,50 @@ function wp_api_template_access_controller( $args, $post_type ) { } } add_filter( 'register_post_type_args', 'wp_api_template_access_controller', 10, 2 ); + +/** + * Adds the post classes to the REST API response. + * + * @param array $post The response object data. + * + * @return array + */ +function gutenberg_add_class_list_to_api_response( $post ) { + + if ( ! isset( $post['id'] ) ) { + return array(); + } + + return get_post_class( array(), $post['id'] ); +} + +/** + * Adds the post classes to public post types in the REST API. + */ +function gutenberg_add_class_list_to_public_post_types() { + $post_types = get_post_types( + array( + 'public' => true, + 'show_in_rest' => true, + ), + 'names' + ); + + if ( ! empty( $post_types ) ) { + register_rest_field( + $post_types, + 'class_list', + array( + 'get_callback' => 'gutenberg_add_class_list_to_api_response', + 'schema' => array( + 'description' => __( 'An array of the class names for the post container element.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ) + ); + } +} +add_action( 'rest_api_init', 'gutenberg_add_class_list_to_public_post_types' ); diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index b15260d1f3e3f..51fa9fe9c33af 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -28,6 +28,9 @@ function gutenberg_enable_experiments() { if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' ); } + if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) { + wp_add_inline_script( 'wp-block-library', 'window.__experimentalFullPageClientSideNavigation = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experimental/full-page-client-side-navigation.php b/lib/experimental/full-page-client-side-navigation.php new file mode 100644 index 0000000000000..ebfddf4aaf436 --- /dev/null +++ b/lib/experimental/full-page-client-side-navigation.php @@ -0,0 +1,57 @@ + 'fullPage' ) ); + wp_enqueue_script_module( '@wordpress/interactivity-router' ); +} + +add_action( 'wp_enqueue_scripts', '_gutenberg_enqueue_interactivity_router' ); + +/** + * Set enhancedPagination attribute for query loop when the experiment is enabled. + * + * @param array $parsed_block The parsed block. + * + * @return array The same parsed block with the modified attribute. + */ +function _gutenberg_add_enhanced_pagination_to_query_block( $parsed_block ) { + if ( 'core/query' !== $parsed_block['blockName'] ) { + return $parsed_block; + } + + $parsed_block['attrs']['enhancedPagination'] = true; + return $parsed_block; +} + +add_filter( 'render_block_data', '_gutenberg_add_enhanced_pagination_to_query_block' ); + +/** + * Add directives to all links. + * + * Note: This should probably be done per site, not by default when this option is enabled. + * + * @param array $content The block content. + * + * @return array The same block content with the directives needed. + */ +function _gutenberg_add_client_side_navigation_directives( $content ) { + $p = new WP_HTML_Tag_Processor( $content ); + // Hack to add the necessary directives to the body tag. + // TODO: Find a proper way to add directives to the body tag. + static $body_interactive_added; + if ( ! $body_interactive_added ) { + $body_interactive_added = true; + return (string) $p . ''; + } + return (string) $p; +} + +// TODO: Explore moving this to the server directive processing. +add_filter( 'render_block', '_gutenberg_add_client_side_navigation_directives' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 4099ea3b0c863..7dfe6fbe5bb90 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -127,6 +127,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-full-page-client-side-navigation', + __( 'Enable full page client-side navigation', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable full page client-side navigation using the Interactivity API', 'gutenberg' ), + 'id' => 'gutenberg-full-page-client-side-navigation', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/lib/load.php b/lib/load.php index ead9a1b9743fb..d556dd7f21b43 100644 --- a/lib/load.php +++ b/lib/load.php @@ -125,12 +125,14 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.5/script-loader.php'; // WordPress 6.6 compat. +require __DIR__ . '/compat/wordpress-6.6/compat.php'; require __DIR__ . '/compat/wordpress-6.6/resolve-patterns.php'; require __DIR__ . '/compat/wordpress-6.6/block-bindings/pattern-overrides.php'; require __DIR__ . '/compat/wordpress-6.6/block-template-utils.php'; require __DIR__ . '/compat/wordpress-6.6/option.php'; require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; +require __DIR__ . '/compat/wordpress-6.6/post.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; @@ -199,6 +201,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/demo.php'; require __DIR__ . '/experiments-page.php'; require __DIR__ . '/interactivity-api.php'; +if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) { + require __DIR__ . '/experimental/full-page-client-side-navigation.php'; +} // Copied package PHP files. if ( is_dir( __DIR__ . '/../build/style-engine' ) ) { diff --git a/lib/theme.json b/lib/theme.json index ece76b5f63cb2..90a5d975e68d6 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -1,6 +1,6 @@ { - "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "$schema": "../schemas/json/theme.json", + "version": 3, "settings": { "appearanceTools": false, "useRootPaddingAwareAlignments": false, @@ -236,6 +236,7 @@ }, "typography": { "customFontSize": true, + "defaultFontSizes": true, "dropCap": true, "fontSizes": [ { diff --git a/package-lock.json b/package-lock.json index 54d6545fdf2cb..9c6d856abe939 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "18.1.2", + "version": "18.3.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "18.1.2", + "version": "18.3.0-rc.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -115,11 +115,11 @@ "@storybook/source-loader": "7.6.15", "@storybook/theming": "7.6.15", "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "14.0.0", + "@testing-library/react": "14.3.0", "@testing-library/react-native": "12.4.3", "@testing-library/user-event": "14.4.3", - "@types/eslint": "7.28.0", - "@types/estree": "0.0.50", + "@types/eslint": "8.56.9", + "@types/estree": "1.0.5", "@types/istanbul-lib-report": "3.0.0", "@types/mime": "2.0.3", "@types/npm-package-arg": "6.1.1", @@ -219,12 +219,12 @@ "postcss-loader": "6.2.1", "prettier": "npm:wp-prettier@3.0.3", "progress": "2.0.3", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "react-native": "0.73.3", "react-native-url-polyfill": "1.1.2", "react-refresh": "0.14.0", - "react-test-renderer": "18.2.0", + "react-test-renderer": "18.3.1", "reassure": "0.7.1", "redux": "4.1.2", "resize-observer-polyfill": "1.5.1", @@ -242,7 +242,7 @@ "strip-json-comments": "5.0.0", "style-loader": "3.2.1", "terser-webpack-plugin": "5.3.9", - "typescript": "5.1.6", + "typescript": "5.4.5", "uglify-js": "3.13.7", "uuid": "9.0.1", "webdriverio": "8.16.20", @@ -15310,9 +15310,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", - "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.0.tgz", + "integrity": "sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -15602,9 +15602,9 @@ "dev": true }, "node_modules/@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", + "version": "8.56.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", + "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", "dev": true, "dependencies": { "@types/estree": "*", @@ -15622,9 +15622,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/events": { @@ -21876,11 +21876,6 @@ "node": ">=0.10.0" } }, - "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -22157,6 +22152,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/cmd-shim": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", @@ -43340,11 +43343,6 @@ "node": ">= 14" } }, - "node_modules/proxy-compare": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz", - "integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==" - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -43807,9 +43805,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -43913,15 +43911,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-element-to-jsx-string": { @@ -44507,23 +44505,23 @@ } }, "node_modules/react-test-renderer": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", "dev": true, "dependencies": { - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-test-renderer/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, "node_modules/read": { @@ -46125,9 +46123,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -49698,9 +49696,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -50511,46 +50509,6 @@ "integrity": "sha512-PnFM3xiZ+kYmLyTiMgTYmU7ZHkjBZz2/+F0DaALc/uUtVzdCt1wAosvYJ5hFQi/hz8O4zb52FQhHZRC+uVkJ+g==", "dev": true }, - "node_modules/valtio": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.7.0.tgz", - "integrity": "sha512-3Tnix66EERwMcrl1rfB3ylcewOcL5L/GiPmC3FlVNreQzqf2jufEeqlNmgnLgSGchkEmH3WYVtS+x6Qw4r+yzQ==", - "dependencies": { - "proxy-compare": "2.3.0", - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@babel/helper-module-imports": ">=7.12", - "@babel/types": ">=7.13", - "aslemammad-vite-plugin-macro": ">=1.0.0-alpha.1", - "babel-plugin-macros": ">=3.0", - "react": ">=16.8", - "vite": ">=2.8.6" - }, - "peerDependenciesMeta": { - "@babel/helper-module-imports": { - "optional": true - }, - "@babel/types": { - "optional": true - }, - "aslemammad-vite-plugin-macro": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - }, - "react": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -51996,12 +51954,6 @@ "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", "dev": true }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, "node_modules/webpack/node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -53061,7 +53013,7 @@ }, "packages/a11y": { "name": "@wordpress/a11y", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53074,7 +53026,7 @@ }, "packages/annotations": { "name": "@wordpress/annotations", - "version": "2.55.0", + "version": "2.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53101,7 +53053,7 @@ }, "packages/api-fetch": { "name": "@wordpress/api-fetch", - "version": "6.52.0", + "version": "6.54.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53114,7 +53066,7 @@ }, "packages/autop": { "name": "@wordpress/autop", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -53125,7 +53077,7 @@ }, "packages/babel-plugin-import-jsx-pragma": { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "4.38.0", + "version": "4.40.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -53137,7 +53089,7 @@ }, "packages/babel-plugin-makepot": { "name": "@wordpress/babel-plugin-makepot", - "version": "5.39.0", + "version": "5.41.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -53154,7 +53106,7 @@ }, "packages/babel-preset-default": { "name": "@wordpress/babel-preset-default", - "version": "7.39.0", + "version": "7.41.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -53169,7 +53121,7 @@ "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", - "react": "^18.2.0" + "react": "^18.3.0" }, "engines": { "node": ">=14" @@ -53177,13 +53129,13 @@ }, "packages/base-styles": { "name": "@wordpress/base-styles", - "version": "4.46.0", + "version": "4.48.0", "dev": true, "license": "GPL-2.0-or-later" }, "packages/blob": { "name": "@wordpress/blob", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -53194,7 +53146,7 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53229,7 +53181,7 @@ }, "packages/block-editor": { "name": "@wordpress/block-editor", - "version": "12.23.0", + "version": "12.25.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53266,7 +53218,7 @@ "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "deepmerge": "^4.3.0", "diff": "^4.0.2", @@ -53342,7 +53294,7 @@ }, "packages/block-library": { "name": "@wordpress/block-library", - "version": "8.32.0", + "version": "8.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53367,6 +53319,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/interactivity": "file:../interactivity", "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/keycodes": "file:../keycodes", "@wordpress/notices": "file:../notices", "@wordpress/patterns": "file:../patterns", @@ -53379,7 +53332,7 @@ "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", @@ -53406,7 +53359,7 @@ }, "packages/block-serialization-default-parser": { "name": "@wordpress/block-serialization-default-parser", - "version": "4.55.0", + "version": "4.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -53417,7 +53370,7 @@ }, "packages/block-serialization-spec-parser": { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.55.0", + "version": "4.57.0", "license": "GPL-2.0-or-later", "dependencies": { "pegjs": "^0.10.0", @@ -53429,7 +53382,7 @@ }, "packages/blocks": { "name": "@wordpress/blocks", - "version": "12.32.0", + "version": "12.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53454,7 +53407,7 @@ "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "react-is": "^18.2.0", + "react-is": "^18.3.0", "remove-accents": "^0.5.0", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", @@ -53468,9 +53421,9 @@ } }, "packages/blocks/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "packages/blocks/node_modules/uuid": { "version": "8.3.2", @@ -53482,7 +53435,7 @@ }, "packages/browserslist-config": { "name": "@wordpress/browserslist-config", - "version": "5.38.0", + "version": "5.40.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -53491,7 +53444,7 @@ }, "packages/commands": { "name": "@wordpress/commands", - "version": "0.26.0", + "version": "0.28.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53502,7 +53455,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "cmdk": "^0.2.0" }, "engines": { @@ -53515,7 +53468,7 @@ }, "packages/components": { "name": "@wordpress/components", - "version": "27.3.0", + "version": "27.5.0", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.3.12", @@ -53548,7 +53501,7 @@ "@wordpress/rich-text": "file:../rich-text", "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", @@ -53564,8 +53517,7 @@ "react-colorful": "^5.3.1", "remove-accents": "^0.5.0", "use-lilius": "^2.0.5", - "uuid": "^9.0.1", - "valtio": "1.7.0" + "uuid": "^9.0.1" }, "engines": { "node": ">=12" @@ -53618,7 +53570,7 @@ }, "packages/compose": { "name": "@wordpress/compose", - "version": "6.32.0", + "version": "6.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53654,7 +53606,7 @@ }, "packages/core-commands": { "name": "@wordpress/core-commands", - "version": "0.24.0", + "version": "0.26.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53680,7 +53632,7 @@ }, "packages/core-data": { "name": "@wordpress/core-data", - "version": "6.32.0", + "version": "6.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53723,7 +53675,7 @@ }, "packages/create-block": { "name": "@wordpress/create-block", - "version": "4.39.0", + "version": "4.41.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -53745,19 +53697,19 @@ "wp-create-block": "index.js" }, "engines": { - "node": ">=14", - "npm": ">=6.14.4" + "node": ">=18", + "npm": ">=10.5.0" } }, "packages/create-block-tutorial-template": { "name": "@wordpress/create-block-tutorial-template", - "version": "3.9.0", + "version": "3.11.0", "dev": true, "license": "GPL-2.0-or-later" }, "packages/customize-widgets": { "name": "@wordpress/customize-widgets", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53781,7 +53733,7 @@ "@wordpress/preferences": "file:../preferences", "@wordpress/private-apis": "file:../private-apis", "@wordpress/widgets": "file:../widgets", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" }, "engines": { @@ -53794,7 +53746,7 @@ }, "packages/data": { "name": "@wordpress/data", - "version": "9.25.0", + "version": "9.27.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53822,7 +53774,7 @@ }, "packages/data-controls": { "name": "@wordpress/data-controls", - "version": "3.24.0", + "version": "3.26.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53839,7 +53791,7 @@ }, "packages/dataviews": { "name": "@wordpress/dataviews", - "version": "0.9.0", + "version": "1.1.0", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.3.12", @@ -53853,7 +53805,7 @@ "@wordpress/keycodes": "file:../keycodes", "@wordpress/primitives": "file:../primitives", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, "engines": { @@ -53900,7 +53852,7 @@ }, "packages/date": { "name": "@wordpress/date", - "version": "4.55.0", + "version": "4.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53914,7 +53866,7 @@ }, "packages/dependency-extraction-webpack-plugin": { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "5.6.0", + "version": "5.8.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -53929,7 +53881,7 @@ }, "packages/deprecated": { "name": "@wordpress/deprecated", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53941,7 +53893,7 @@ }, "packages/docgen": { "name": "@wordpress/docgen", - "version": "1.64.0", + "version": "1.66.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -53959,7 +53911,7 @@ }, "packages/dom": { "name": "@wordpress/dom", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -53971,7 +53923,7 @@ }, "packages/dom-ready": { "name": "@wordpress/dom-ready", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -53982,7 +53934,7 @@ }, "packages/e2e-test-utils": { "name": "@wordpress/e2e-test-utils", - "version": "10.26.0", + "version": "10.28.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54004,7 +53956,7 @@ }, "packages/e2e-test-utils-playwright": { "name": "@wordpress/e2e-test-utils-playwright", - "version": "0.23.0", + "version": "0.25.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54027,7 +53979,7 @@ }, "packages/e2e-tests": { "name": "@wordpress/e2e-tests", - "version": "7.26.0", + "version": "7.28.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54066,7 +54018,7 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "7.32.0", + "version": "7.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54099,7 +54051,7 @@ "@wordpress/viewport": "file:../viewport", "@wordpress/warning": "file:../warning", "@wordpress/widgets": "file:../widgets", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "memize": "^2.1.0" }, "engines": { @@ -54112,7 +54064,7 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "5.32.0", + "version": "5.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54158,8 +54110,8 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", "client-zip": "^2.4.4", + "clsx": "^2.1.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", "fast-deep-equal": "^3.1.3", @@ -54177,7 +54129,7 @@ }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", - "version": "5.32.0", + "version": "5.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54207,7 +54159,7 @@ "@wordpress/reusable-blocks": "file:../reusable-blocks", "@wordpress/url": "file:../url", "@wordpress/widgets": "file:../widgets", - "classnames": "^2.3.1" + "clsx": "^2.1.1" }, "engines": { "node": ">=12" @@ -54219,7 +54171,7 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "13.32.0", + "version": "13.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54256,7 +54208,7 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "date-fns": "^3.6.0", "memize": "^2.1.0", "react-autosize-textarea": "^7.1.0", @@ -54272,7 +54224,7 @@ }, "packages/element": { "name": "@wordpress/element", - "version": "5.32.0", + "version": "5.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54281,8 +54233,8 @@ "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" }, "engines": { "node": ">=12" @@ -54290,7 +54242,7 @@ }, "packages/env": { "name": "@wordpress/env", - "version": "9.7.0", + "version": "9.9.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54427,7 +54379,7 @@ }, "packages/escape-html": { "name": "@wordpress/escape-html", - "version": "2.55.0", + "version": "2.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54438,7 +54390,7 @@ }, "packages/eslint-plugin": { "name": "@wordpress/eslint-plugin", - "version": "17.12.0", + "version": "18.0.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54481,7 +54433,7 @@ }, "packages/format-library": { "name": "@wordpress/format-library", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54508,7 +54460,7 @@ }, "packages/hooks": { "name": "@wordpress/hooks", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54519,7 +54471,7 @@ }, "packages/html-entities": { "name": "@wordpress/html-entities", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54530,7 +54482,7 @@ }, "packages/i18n": { "name": "@wordpress/i18n", - "version": "4.55.0", + "version": "4.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54549,7 +54501,7 @@ }, "packages/icons": { "name": "@wordpress/icons", - "version": "9.46.0", + "version": "9.48.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54562,7 +54514,7 @@ }, "packages/interactivity": { "name": "@wordpress/interactivity", - "version": "5.4.0", + "version": "5.6.0", "license": "GPL-2.0-or-later", "dependencies": { "@preact/signals": "^1.2.2", @@ -54575,7 +54527,7 @@ }, "packages/interactivity-router": { "name": "@wordpress/interactivity-router", - "version": "1.5.0", + "version": "1.7.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/interactivity": "file:../interactivity" @@ -54635,7 +54587,7 @@ }, "packages/interface": { "name": "@wordpress/interface", - "version": "5.32.0", + "version": "5.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54651,7 +54603,7 @@ "@wordpress/preferences": "file:../preferences", "@wordpress/private-apis": "file:../private-apis", "@wordpress/viewport": "file:../viewport", - "classnames": "^2.3.1" + "clsx": "^2.1.1" }, "engines": { "node": ">=12" @@ -54663,7 +54615,7 @@ }, "packages/is-shallow-equal": { "name": "@wordpress/is-shallow-equal", - "version": "4.55.0", + "version": "4.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54674,7 +54626,7 @@ }, "packages/jest-console": { "name": "@wordpress/jest-console", - "version": "7.26.0", + "version": "7.28.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54690,7 +54642,7 @@ }, "packages/jest-preset-default": { "name": "@wordpress/jest-preset-default", - "version": "11.26.0", + "version": "11.28.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54707,7 +54659,7 @@ }, "packages/jest-puppeteer-axe": { "name": "@wordpress/jest-puppeteer-axe", - "version": "6.26.0", + "version": "6.28.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54729,7 +54681,7 @@ }, "packages/keyboard-shortcuts": { "name": "@wordpress/keyboard-shortcuts", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54746,7 +54698,7 @@ }, "packages/keycodes": { "name": "@wordpress/keycodes", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54758,7 +54710,7 @@ }, "packages/lazy-import": { "name": "@wordpress/lazy-import", - "version": "1.42.0", + "version": "1.44.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54772,7 +54724,7 @@ }, "packages/list-reusable-blocks": { "name": "@wordpress/list-reusable-blocks", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54794,7 +54746,7 @@ }, "packages/media-utils": { "name": "@wordpress/media-utils", - "version": "4.46.0", + "version": "4.48.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54809,7 +54761,7 @@ }, "packages/notices": { "name": "@wordpress/notices", - "version": "4.23.0", + "version": "4.25.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54825,7 +54777,7 @@ }, "packages/npm-package-json-lint-config": { "name": "@wordpress/npm-package-json-lint-config", - "version": "4.40.0", + "version": "4.42.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -54837,7 +54789,7 @@ }, "packages/nux": { "name": "@wordpress/nux", - "version": "8.17.0", + "version": "8.19.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54859,7 +54811,7 @@ }, "packages/patterns": { "name": "@wordpress/patterns", - "version": "1.16.0", + "version": "1.18.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54888,7 +54840,7 @@ }, "packages/plugins": { "name": "@wordpress/plugins", - "version": "6.23.0", + "version": "6.25.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54910,7 +54862,7 @@ }, "packages/postcss-plugins-preset": { "name": "@wordpress/postcss-plugins-preset", - "version": "4.39.0", + "version": "4.41.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54926,7 +54878,7 @@ }, "packages/postcss-themes": { "name": "@wordpress/postcss-themes", - "version": "5.38.0", + "version": "5.40.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -54938,7 +54890,7 @@ }, "packages/preferences": { "name": "@wordpress/preferences", - "version": "3.32.0", + "version": "3.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54951,7 +54903,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1" + "clsx": "^2.1.1" }, "engines": { "node": ">=12" @@ -54963,7 +54915,7 @@ }, "packages/preferences-persistence": { "name": "@wordpress/preferences-persistence", - "version": "1.47.0", + "version": "1.49.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54975,7 +54927,7 @@ }, "packages/prettier-config": { "name": "@wordpress/prettier-config", - "version": "3.12.0", + "version": "3.14.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -54987,12 +54939,12 @@ }, "packages/primitives": { "name": "@wordpress/primitives", - "version": "3.53.0", + "version": "3.55.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/element": "file:../element", - "classnames": "^2.3.1" + "clsx": "^2.1.1" }, "engines": { "node": ">=12" @@ -55000,7 +54952,7 @@ }, "packages/priority-queue": { "name": "@wordpress/priority-queue", - "version": "2.55.0", + "version": "2.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55012,7 +54964,7 @@ }, "packages/private-apis": { "name": "@wordpress/private-apis", - "version": "0.37.0", + "version": "0.39.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55023,7 +54975,7 @@ }, "packages/project-management-automation": { "name": "@wordpress/project-management-automation", - "version": "1.54.0", + "version": "1.56.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55036,7 +54988,7 @@ }, "packages/react-i18n": { "name": "@wordpress/react-i18n", - "version": "3.53.0", + "version": "3.55.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55050,7 +55002,7 @@ }, "packages/react-native-aztec": { "name": "@wordpress/react-native-aztec", - "version": "1.117.0", + "version": "1.118.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/element": "file:../element", @@ -55063,7 +55015,7 @@ }, "packages/react-native-bridge": { "name": "@wordpress/react-native-bridge", - "version": "1.117.0", + "version": "1.118.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/react-native-aztec": "file:../react-native-aztec" @@ -55074,7 +55026,7 @@ }, "packages/react-native-editor": { "name": "@wordpress/react-native-editor", - "version": "1.117.0", + "version": "1.118.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55183,7 +55135,7 @@ }, "packages/readable-js-assets-webpack-plugin": { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "2.38.0", + "version": "2.40.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -55195,7 +55147,7 @@ }, "packages/redux-routine": { "name": "@wordpress/redux-routine", - "version": "4.55.0", + "version": "4.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55239,7 +55191,7 @@ }, "packages/reusable-blocks": { "name": "@wordpress/reusable-blocks", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55265,7 +55217,7 @@ }, "packages/rich-text": { "name": "@wordpress/rich-text", - "version": "6.32.0", + "version": "6.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55288,7 +55240,7 @@ }, "packages/router": { "name": "@wordpress/router", - "version": "0.24.0", + "version": "0.26.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55306,7 +55258,7 @@ }, "packages/scripts": { "name": "@wordpress/scripts", - "version": "27.6.0", + "version": "27.8.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55691,7 +55643,7 @@ }, "packages/server-side-render": { "name": "@wordpress/server-side-render", - "version": "4.32.0", + "version": "4.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55716,7 +55668,7 @@ }, "packages/shortcode": { "name": "@wordpress/shortcode", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55728,7 +55680,7 @@ }, "packages/style-engine": { "name": "@wordpress/style-engine", - "version": "1.38.0", + "version": "1.40.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55740,7 +55692,7 @@ }, "packages/stylelint-config": { "name": "@wordpress/stylelint-config", - "version": "21.38.0", + "version": "21.40.0", "dev": true, "license": "MIT", "dependencies": { @@ -55756,7 +55708,7 @@ }, "packages/sync": { "name": "@wordpress/sync", - "version": "0.17.0", + "version": "0.19.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55776,7 +55728,7 @@ }, "packages/token-list": { "name": "@wordpress/token-list", - "version": "2.55.0", + "version": "2.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55787,7 +55739,7 @@ }, "packages/undo-manager": { "name": "@wordpress/undo-manager", - "version": "0.15.0", + "version": "0.17.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55799,7 +55751,7 @@ }, "packages/url": { "name": "@wordpress/url", - "version": "3.56.0", + "version": "3.58.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55811,7 +55763,7 @@ }, "packages/viewport": { "name": "@wordpress/viewport", - "version": "5.32.0", + "version": "5.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55828,7 +55780,7 @@ }, "packages/warning": { "name": "@wordpress/warning", - "version": "2.55.0", + "version": "2.57.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=12" @@ -55836,7 +55788,7 @@ }, "packages/widgets": { "name": "@wordpress/widgets", - "version": "3.32.0", + "version": "3.34.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55851,7 +55803,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", - "classnames": "^2.3.1" + "clsx": "^2.1.1" }, "peerDependencies": { "react": "^18.0.0", @@ -55860,7 +55812,7 @@ }, "packages/wordcount": { "name": "@wordpress/wordcount", - "version": "3.55.0", + "version": "3.57.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -66353,9 +66305,9 @@ } }, "@testing-library/react": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", - "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.0.tgz", + "integrity": "sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", @@ -66607,9 +66559,9 @@ "dev": true }, "@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", + "version": "8.56.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", + "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", "dev": true, "requires": { "@types/estree": "*", @@ -66627,9 +66579,9 @@ } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "@types/events": { @@ -68541,7 +68493,7 @@ "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", - "react": "^18.2.0" + "react": "^18.3.0" } }, "@wordpress/base-styles": { @@ -68616,7 +68568,7 @@ "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "deepmerge": "^4.3.0", "diff": "^4.0.2", @@ -68686,6 +68638,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/interactivity": "file:../interactivity", "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/keycodes": "file:../keycodes", "@wordpress/notices": "file:../notices", "@wordpress/patterns": "file:../patterns", @@ -68698,7 +68651,7 @@ "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", @@ -68752,7 +68705,7 @@ "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "react-is": "^18.2.0", + "react-is": "^18.3.0", "remove-accents": "^0.5.0", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", @@ -68760,9 +68713,9 @@ }, "dependencies": { "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "uuid": { "version": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -68784,7 +68737,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "cmdk": "^0.2.0" } }, @@ -68821,7 +68774,7 @@ "@wordpress/rich-text": "file:../rich-text", "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", @@ -68837,8 +68790,7 @@ "react-colorful": "^5.3.1", "remove-accents": "^0.5.0", "use-lilius": "^2.0.5", - "uuid": "^9.0.1", - "valtio": "1.7.0" + "uuid": "^9.0.1" }, "dependencies": { "@floating-ui/react-dom": { @@ -68997,7 +68949,7 @@ "@wordpress/preferences": "file:../preferences", "@wordpress/private-apis": "file:../private-apis", "@wordpress/widgets": "file:../widgets", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" } }, @@ -69044,7 +68996,7 @@ "@wordpress/keycodes": "file:../keycodes", "@wordpress/primitives": "file:../primitives", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, "dependencies": { @@ -69203,7 +69155,7 @@ "@wordpress/viewport": "file:../viewport", "@wordpress/warning": "file:../warning", "@wordpress/widgets": "file:../widgets", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "memize": "^2.1.0" } }, @@ -69253,8 +69205,8 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", "client-zip": "^2.4.4", + "clsx": "^2.1.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", "fast-deep-equal": "^3.1.3", @@ -69293,7 +69245,7 @@ "@wordpress/reusable-blocks": "file:../reusable-blocks", "@wordpress/url": "file:../url", "@wordpress/widgets": "file:../widgets", - "classnames": "^2.3.1" + "clsx": "^2.1.1" } }, "@wordpress/editor": { @@ -69333,7 +69285,7 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "date-fns": "^3.6.0", "memize": "^2.1.0", "react-autosize-textarea": "^7.1.0", @@ -69349,8 +69301,8 @@ "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } }, "@wordpress/env": { @@ -69585,7 +69537,7 @@ "@wordpress/preferences": "file:../preferences", "@wordpress/private-apis": "file:../private-apis", "@wordpress/viewport": "file:../viewport", - "classnames": "^2.3.1" + "clsx": "^2.1.1" } }, "@wordpress/is-shallow-equal": { @@ -69742,7 +69694,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1" + "clsx": "^2.1.1" } }, "@wordpress/preferences-persistence": { @@ -69760,7 +69712,7 @@ "requires": { "@babel/runtime": "^7.16.0", "@wordpress/element": "file:../element", - "classnames": "^2.3.1" + "clsx": "^2.1.1" } }, "@wordpress/priority-queue": { @@ -70349,7 +70301,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", - "classnames": "^2.3.1" + "clsx": "^2.1.1" } }, "@wordpress/wordcount": { @@ -73065,11 +73017,6 @@ } } }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -73285,6 +73232,11 @@ "mimic-response": "^1.0.0" } }, + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "cmd-shim": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", @@ -89427,11 +89379,6 @@ } } }, - "proxy-compare": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz", - "integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==" - }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -89771,9 +89718,9 @@ } }, "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "requires": { "loose-envify": "^1.1.0" } @@ -89853,12 +89800,12 @@ "dev": true }, "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "requires": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" } }, "react-element-to-jsx-string": { @@ -90288,20 +90235,20 @@ } }, "react-test-renderer": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", "dev": true, "requires": { - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "dependencies": { "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true } } @@ -91533,9 +91480,9 @@ } }, "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "requires": { "loose-envify": "^1.1.0" } @@ -94313,9 +94260,9 @@ "dev": true }, "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true }, "uc.micro": { @@ -94930,15 +94877,6 @@ "integrity": "sha512-PnFM3xiZ+kYmLyTiMgTYmU7ZHkjBZz2/+F0DaALc/uUtVzdCt1wAosvYJ5hFQi/hz8O4zb52FQhHZRC+uVkJ+g==", "dev": true }, - "valtio": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.7.0.tgz", - "integrity": "sha512-3Tnix66EERwMcrl1rfB3ylcewOcL5L/GiPmC3FlVNreQzqf2jufEeqlNmgnLgSGchkEmH3WYVtS+x6Qw4r+yzQ==", - "requires": { - "proxy-compare": "2.3.0", - "use-sync-external-store": "1.2.0" - } - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -95610,12 +95548,6 @@ "webpack-sources": "^3.2.3" }, "dependencies": { - "@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, "@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", diff --git a/package.json b/package.json index 59a7cefe88170..ff76bff3cf07d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "18.1.2", + "version": "18.3.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -127,11 +127,11 @@ "@storybook/source-loader": "7.6.15", "@storybook/theming": "7.6.15", "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "14.0.0", + "@testing-library/react": "14.3.0", "@testing-library/react-native": "12.4.3", "@testing-library/user-event": "14.4.3", - "@types/eslint": "7.28.0", - "@types/estree": "0.0.50", + "@types/eslint": "8.56.9", + "@types/estree": "1.0.5", "@types/istanbul-lib-report": "3.0.0", "@types/mime": "2.0.3", "@types/npm-package-arg": "6.1.1", @@ -231,12 +231,12 @@ "postcss-loader": "6.2.1", "prettier": "npm:wp-prettier@3.0.3", "progress": "2.0.3", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "react-native": "0.73.3", "react-native-url-polyfill": "1.1.2", "react-refresh": "0.14.0", - "react-test-renderer": "18.2.0", + "react-test-renderer": "18.3.1", "reassure": "0.7.1", "redux": "4.1.2", "resize-observer-polyfill": "1.5.1", @@ -254,7 +254,7 @@ "strip-json-comments": "5.0.0", "style-loader": "3.2.1", "terser-webpack-plugin": "5.3.9", - "typescript": "5.1.6", + "typescript": "5.4.5", "uglify-js": "3.13.7", "uuid": "9.0.1", "webdriverio": "8.16.20", diff --git a/packages/a11y/CHANGELOG.md b/packages/a11y/CHANGELOG.md index f9eb9aedf15b4..d660609b46f54 100644 --- a/packages/a11y/CHANGELOG.md +++ b/packages/a11y/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 3.57.0 (2024-05-02) + +## 3.56.0 (2024-04-19) + ## 3.55.0 (2024-04-03) ## 3.54.0 (2024-03-21) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 80375c5519f7e..2ac495eea2499 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "3.55.0", + "version": "3.57.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index 21117c194d2a5..cb74ffb7ed766 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 2.57.0 (2024-05-02) + +## 2.56.0 (2024-04-19) + ## 2.55.0 (2024-04-03) ## 2.54.0 (2024-03-21) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 571be01d55046..4092aeeecd944 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "2.55.0", + "version": "2.57.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 048023e03d9a1..af273c9cd9099 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 6.54.0 (2024-05-02) + +## 6.53.0 (2024-04-19) + ## 6.52.0 (2024-04-03) ## 6.51.0 (2024-03-21) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 09431da66bbe0..4addcbff90d56 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "6.52.0", + "version": "6.54.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index 8459618c4df4f..b209316d90031 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 3.57.0 (2024-05-02) + +## 3.56.0 (2024-04-19) + ## 3.55.0 (2024-04-03) ## 3.54.0 (2024-03-21) diff --git a/packages/autop/package.json b/packages/autop/package.json index de808d270ff36..470c6177f2ee3 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "3.55.0", + "version": "3.57.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 4a0d59cf95bd5..877d75ab6cd8e 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 4.40.0 (2024-05-02) + +## 4.39.0 (2024-04-19) + ## 4.38.0 (2024-04-03) ## 4.37.0 (2024-03-21) diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index efa45964304e3..d60ef469091a7 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "4.38.0", + "version": "4.40.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index 66b8bdf881e22..528d6258b7a43 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 5.41.0 (2024-05-02) + +## 5.40.0 (2024-04-19) + ## 5.39.0 (2024-04-03) ## 5.38.0 (2024-03-21) diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 78256f730ba49..dd914083cbc02 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "5.39.0", + "version": "5.41.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 586ea36a9f341..b3f84606c6fcf 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 7.41.0 (2024-05-02) + +## 7.40.0 (2024-04-19) + ## 7.39.0 (2024-04-03) ## 7.38.0 (2024-03-21) diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index ffd759abfcd48..d8ceef3011dce 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "7.39.0", + "version": "7.41.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -40,7 +40,7 @@ "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", - "react": "^18.2.0" + "react": "^18.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/base-styles/CHANGELOG.md b/packages/base-styles/CHANGELOG.md index 193e8781b6cc6..c4db95ca03eed 100644 --- a/packages/base-styles/CHANGELOG.md +++ b/packages/base-styles/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 4.48.0 (2024-05-02) + +## 4.47.0 (2024-04-19) + ## 4.46.0 (2024-04-03) ## 4.45.0 (2024-03-21) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index b5d6beabb4f60..1191c16670ba1 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -4,6 +4,7 @@ $z-layers: ( ".block-editor-block-list__block::before": 0, + ".block-editor-block-list__block.is-selected": 20, ".block-editor-block-switcher__arrow": 1, ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 31, // When scrolled to top this toolbar needs to sit over block-editor-block-toolbar @@ -87,6 +88,7 @@ $z-layers: ( // #wpadminbar { z-index: 99999 } ".interface-interface-skeleton__sidebar": 100000, ".edit-post-layout__toggle-sidebar-panel": 100000, + ".editor-layout__toggle-sidebar-panel": 100000, ".edit-widgets-sidebar": 100000, ".edit-post-layout .edit-post-post-publish-panel": 100001, // For larger views, the wp-admin navbar dropdown should be at top of diff --git a/packages/base-styles/package.json b/packages/base-styles/package.json index e333ec49de511..17817fd9aea24 100644 --- a/packages/base-styles/package.json +++ b/packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/base-styles", - "version": "4.46.0", + "version": "4.48.0", "description": "Base SCSS utilities and variables for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/CHANGELOG.md b/packages/blob/CHANGELOG.md index fd1bf5a5ad127..5cafe71e013c9 100644 --- a/packages/blob/CHANGELOG.md +++ b/packages/blob/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 3.57.0 (2024-05-02) + +## 3.56.0 (2024-04-19) + ## 3.55.0 (2024-04-03) ## 3.54.0 (2024-03-21) diff --git a/packages/blob/package.json b/packages/blob/package.json index 28a33bd18fac4..4295232c2731f 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "3.55.0", + "version": "3.57.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index 2d15643bbb32a..2a0743bac6a3f 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 4.34.0 (2024-05-02) + +## 4.33.0 (2024-04-19) + ## 4.32.0 (2024-04-03) ## 4.31.0 (2024-03-21) diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 5f50ec3ebe667..7cd64958fca41 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "4.32.0", + "version": "4.34.0", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js index 18511a93923f8..75328db7c554d 100644 --- a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js +++ b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js @@ -11,9 +11,9 @@ import { blockDefault } from '@wordpress/icons'; import CompactList from '../../components/compact-list'; import { store as blockDirectoryStore } from '../../store'; -// We shouldn't import the edit-post package directly -// because it would include the wp-edit-post in all pages loading the block-directory script. -const { PluginPrePublishPanel } = window?.wp?.editPost ?? {}; +// We shouldn't import the editor package directly +// because it would include the wp-editor in all pages loading the block-directory script. +const { PluginPrePublishPanel } = window?.wp?.editor ?? {}; export default function InstalledBlocksPrePublishPanel() { const newBlockTypes = useSelect( diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index c471eb84ace9a..a8c3cebbe8f1c 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Internal + +- Replaced `classnames` package with the faster and smaller `clsx` package ([#61138](https://github.com/WordPress/gutenberg/pull/61138)). + +## 12.25.0 (2024-05-02) + +## 12.24.0 (2024-04-19) + ## 12.23.0 (2024-04-03) ## 12.22.0 (2024-03-21) @@ -16,7 +24,7 @@ ## 12.18.0 (2024-01-24) -- Deprecated `__experimentalRecursionProvider` and `__experimentalUseHasRecursion` in favor of their new stable counterparts `RecursionProvider` and `useHasRecursion`. +- Deprecated `__experimentalRecursionProvider` and `__experimentalUseHasRecursion` in favor of their new stable counterparts `RecursionProvider` and `useHasRecursion`. ## 12.17.0 (2024-01-10) @@ -247,8 +255,8 @@ ### Breaking Changes -- Drop support for Internet Explorer 11 ([#31110](https://github.com/WordPress/gutenberg/pull/31110)). Learn more at https://make.wordpress.org/core/2021/04/22/ie-11-support-phase-out-plan/. -- Increase the minimum Node.js version to v12 matching Long Term Support releases ([#31270](https://github.com/WordPress/gutenberg/pull/31270)). Learn more at https://nodejs.org/en/about/releases/. +- Drop support for Internet Explorer 11 ([#31110](https://github.com/WordPress/gutenberg/pull/31110)). Learn more at . +- Increase the minimum Node.js version to v12 matching Long Term Support releases ([#31270](https://github.com/WordPress/gutenberg/pull/31270)). Learn more at . ## 5.3.0 (2021-03-17) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 75f4d1143895e..6ac825da4925e 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -17,9 +17,9 @@ _This package assumes that your code will run in an **ES2015+** environment. If ```js import { useState } from 'react'; import { + BlockCanvas, BlockEditorProvider, BlockList, - WritingFlow, } from '@wordpress/block-editor'; function MyEditorComponent() { @@ -1032,6 +1032,10 @@ _Returns_ A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. +_Parameters_ + +- _zoomOut_ `boolean`: If we should enter into zoomOut mode or not + ### Warning _Related_ diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 5a99129760d3c..26221e597db7d 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "12.23.0", + "version": "12.25.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -65,7 +65,7 @@ "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "deepmerge": "^4.3.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 b41cb4b0d2520..9eb47533c5b7a 100644 --- a/packages/block-editor/src/components/alignment-control/ui.js +++ b/packages/block-editor/src/components/alignment-control/ui.js @@ -45,7 +45,9 @@ function AlignmentUI( { ); function setIcon() { - if ( activeAlignment ) return activeAlignment.icon; + if ( activeAlignment ) { + return activeAlignment.icon; + } return isRTL() ? alignRight : alignLeft; } diff --git a/packages/block-editor/src/components/block-alignment-control/ui.js b/packages/block-editor/src/components/block-alignment-control/ui.js index 9356e59ef465a..b1762547bb815 100644 --- a/packages/block-editor/src/components/block-alignment-control/ui.js +++ b/packages/block-editor/src/components/block-alignment-control/ui.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classNames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -85,7 +85,7 @@ function BlockAlignmentUI( { key={ controlName } icon={ icon } iconPosition="left" - className={ classNames( + className={ clsx( 'components-dropdown-menu__menu-item', { 'is-active': isSelected, diff --git a/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss b/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss index e1ce8bac6064b..4565473ec95eb 100644 --- a/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss +++ b/packages/block-editor/src/components/block-bindings-toolbar-indicator/style.scss @@ -8,9 +8,6 @@ } } -.edit-post-header__toolbar -.selected-block-tools-wrapper -.block-editor-block-toolbar -.block-editor-block-bindings-toolbar-indicator { +.editor-collapsible-block-toolbar .block-editor-block-bindings-toolbar-indicator { height: 32px; } diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 2dc9e32d7a393..6ec97bfb38853 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -2,11 +2,9 @@ iframe[name="editor-canvas"] { width: 100%; height: 100%; display: block; + background-color: $gray-300; } -iframe[name="editor-canvas"]:not(.has-editor-padding) { - background-color: $white; -} iframe[name="editor-canvas"].has-editor-padding { padding: $grid-unit-30 $grid-unit-30 0; diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 4b5478485d87a..3122fb5cf0373 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -45,7 +45,7 @@ function BlockCard( { title, icon, description, blockType, className } ) { const { selectBlock } = useDispatch( blockEditorStore ); return ( -
+
{ parentNavBlockClientId && ( // This is only used by the Navigation block for now. It's not ideal having Navigation block specific code here. -
- - + { __( 'Explore all patterns' ) } +
) } { isMobile && ( { ( category ) => ( - +
+ +
) }
) } { showPatternsExplorer && ( setShowPatternsExplorer( false ) } rootClientId={ rootClientId } diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js index 7a5a9eb8d989e..04f25ade4880d 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js @@ -1,16 +1,8 @@ -/** - * WordPress dependencies - */ -import { useRef, useEffect } from '@wordpress/element'; - -import { focus } from '@wordpress/dom'; - /** * Internal dependencies */ import { PatternCategoryPreviews } from './pattern-category-previews'; -import { useZoomOut } from '../../../hooks/use-zoom-out'; export function PatternCategoryPreviewPanel( { rootClientId, @@ -20,34 +12,15 @@ export function PatternCategoryPreviewPanel( { showTitlesAsTooltip, patternFilter, } ) { - const container = useRef(); - - useEffect( () => { - const timeout = setTimeout( () => { - const [ firstTabbable ] = focus.tabbable.find( container.current ); - firstTabbable?.focus(); - } ); - return () => clearTimeout( timeout ); - }, [ category ] ); - - // Move to zoom out mode when this component is mounted - // and back to the previous mode when unmounted. - useZoomOut(); - return ( -
- -
+ ); } diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index 7b8fd8e76202a..8a4bedbb4a5fc 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -128,7 +128,7 @@ export function PatternCategoryPreviews( { ); return ( -
+ <> ) } -
+ ); } diff --git a/packages/block-editor/src/components/inserter/block-types-tab.js b/packages/block-editor/src/components/inserter/block-types-tab.js index 60e1ff4beb6d5..fc742c3bf6489 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.js @@ -13,6 +13,7 @@ import InserterPanel from './panel'; import useBlockTypesState from './hooks/use-block-types-state'; import InserterListbox from '../inserter-listbox'; import { orderBy } from '../../utils/sorting'; +import InserterNoResults from './no-results'; const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ]; @@ -102,6 +103,10 @@ export function BlockTypesTab( { didRenderAllCategories ? collectionEntries : EMPTY_ARRAY ); + if ( ! items.length ) { + return ; + } + return (
diff --git a/packages/block-editor/src/components/inserter/category-tabs/index.js b/packages/block-editor/src/components/inserter/category-tabs/index.js new file mode 100644 index 0000000000000..47c9f0e051a66 --- /dev/null +++ b/packages/block-editor/src/components/inserter/category-tabs/index.js @@ -0,0 +1,74 @@ +/** + * WordPress dependencies + */ +import { isRTL } from '@wordpress/i18n'; +import { + __experimentalHStack as HStack, + FlexBlock, + privateApis as componentsPrivateApis, +} from '@wordpress/components'; +import { Icon, chevronRight, chevronLeft } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { unlock } from '../../../lock-unlock'; + +const { Tabs } = unlock( componentsPrivateApis ); + +function CategoryTabs( { + categories, + selectedCategory, + onSelectCategory, + children, +} ) { + return ( + { + // Pass the full category object + onSelectCategory( + categories.find( + ( category ) => category.name === categoryId + ) + ); + } } + > + + { categories.map( ( category ) => ( + + + { category.label } + + + + ) ) } + + { categories.map( ( category ) => ( + + { children } + + ) ) } + + ); +} + +export default CategoryTabs; diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 0fdedfca89e7d..d3e7dba0df683 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -207,10 +207,9 @@ class PrivateInserter extends Component { return ( { - const timeout = setTimeout( () => { - const [ firstTabbable ] = focus.tabbable.find( container.current ); - firstTabbable?.focus(); - } ); - return () => clearTimeout( timeout ); - }, [ category ] ); - return ( -
- -
- ); -} - export function MediaCategoryPanel( { rootClientId, onInsert, category } ) { const [ search, setSearch, debouncedSearch ] = useDebouncedInput(); const { mediaList, isLoading } = useMediaResults( category, { diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js index ad6239c61d2f7..816cc9373ea08 100644 --- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js +++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -127,23 +127,31 @@ export function MediaPreview( { media, onClick, category } ) { ); const { createErrorNotice, createSuccessNotice } = useDispatch( noticesStore ); - const mediaUpload = useSelect( - ( select ) => select( blockEditorStore ).getSettings().mediaUpload, - [] - ); + const { getSettings } = useSelect( blockEditorStore ); + const onMediaInsert = useCallback( ( previewBlock ) => { // Prevent multiple uploads when we're in the process of inserting. if ( isInserting ) { return; } + + const settings = getSettings(); const clonedBlock = cloneBlock( previewBlock ); const { id, url, caption } = clonedBlock.attributes; + + // User has no permission to upload media. + if ( ! id && ! settings.mediaUpload ) { + setShowExternalUploadModal( true ); + return; + } + // Media item already exists in library, so just insert it. if ( !! id ) { onClick( clonedBlock ); return; } + setIsInserting( true ); // Media item does not exist in library, so try to upload it. // Fist fetch the image data. This may fail if the image host @@ -154,7 +162,7 @@ export function MediaPreview( { media, onClick, category } ) { .fetch( url ) .then( ( response ) => response.blob() ) .then( ( blob ) => { - mediaUpload( { + settings.mediaUpload( { filesList: [ blob ], additionalData: { caption }, onFileChange( [ img ] ) { @@ -189,10 +197,10 @@ export function MediaPreview( { media, onClick, category } ) { }, [ isInserting, + getSettings, onClick, - mediaUpload, - createErrorNotice, createSuccessNotice, + createErrorNotice, ] ); @@ -214,7 +222,7 @@ export function MediaPreview( { media, onClick, category } ) { { ( { draggable, onDragStart, onDragEnd } ) => (
mediaCategories.map( ( mediaCategory ) => ( { ...mediaCategory, @@ -57,82 +48,52 @@ function MediaTab( { } ) ), [ mediaCategories ] ); + + if ( ! categories.length ) { + return ; + } + return ( <> { ! isMobile && (
-
- - + { __( 'Open Media Library' ) } + + ) } + /> +
) } { isMobile && ( - + { ( category ) => ( {}; function InserterMenu( @@ -58,7 +56,7 @@ function InserterMenu( const [ patternFilter, setPatternFilter ] = useState( 'all' ); const [ selectedMediaCategory, setSelectedMediaCategory ] = useState( null ); - const [ selectedTab, setSelectedTab ] = useState( null ); + const [ selectedTab, setSelectedTab ] = useState( 'blocks' ); const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] = useInsertionPoint( { @@ -68,18 +66,6 @@ function InserterMenu( insertionIndex: __experimentalInsertionIndex, shouldFocusBlock, } ); - const { showPatterns } = useSelect( - ( select ) => { - const { hasAllowedPatterns } = unlock( select( blockEditorStore ) ); - return { - showPatterns: hasAllowedPatterns( destinationRootClientId ), - }; - }, - [ destinationRootClientId ] - ); - - const mediaCategories = useMediaCategories( destinationRootClientId ); - const showMedia = mediaCategories.length > 0; const onInsert = useCallback( ( blocks, meta, shouldForceFocusBlock ) => { @@ -122,11 +108,78 @@ function InserterMenu( __experimentalOnPatternCategorySelection(); } }, - [ setSelectedPatternCategory, __experimentalOnPatternCategorySelection ] + [ + setSelectedPatternCategory, + __experimentalOnPatternCategorySelection, + isZoomedOutViewExperimentEnabled, + ] ); - const blocksTab = useMemo( - () => ( + const showPatternPanel = + selectedTab === 'patterns' && + ! delayedFilterValue && + !! selectedPatternCategory; + + const showMediaPanel = selectedTab === 'media' && !! selectedMediaCategory; + + const inserterSearch = useMemo( () => { + if ( selectedTab === 'media' ) { + return null; + } + + return ( + <> + { + if ( hoveredItem ) { + setHoveredItem( null ); + } + setFilterValue( value ); + } } + value={ filterValue } + label={ __( 'Search for blocks and patterns' ) } + placeholder={ __( 'Search' ) } + /> + { !! delayedFilterValue && ( + + ) } + + ); + }, [ + selectedTab, + hoveredItem, + setHoveredItem, + setFilterValue, + filterValue, + delayedFilterValue, + onSelect, + onHover, + onHoverPattern, + shouldFocusBlock, + clientId, + rootClientId, + __experimentalInsertionIndex, + isAppender, + ] ); + + const blocksTab = useMemo( () => { + return ( <>
) } - ), - [ - destinationRootClientId, - onInsert, - onHover, - showMostUsedBlocks, - showInserterHelpPanel, - ] - ); + ); + }, [ + destinationRootClientId, + onInsert, + onHover, + showMostUsedBlocks, + showInserterHelpPanel, + ] ); - const patternsTab = useMemo( - () => ( + const patternsTab = useMemo( () => { + return ( - ), - [ - destinationRootClientId, - onInsertPattern, - onClickPatternCategory, - selectedPatternCategory, - ] - ); + > + { showPatternPanel && ( + + ) } + + ); + }, [ + destinationRootClientId, + onHoverPattern, + onInsertPattern, + onClickPatternCategory, + patternFilter, + selectedPatternCategory, + showPatternPanel, + ] ); - const mediaTab = useMemo( - () => ( + const mediaTab = useMemo( () => { + return ( - ), - [ - destinationRootClientId, - onInsert, - selectedMediaCategory, - setSelectedMediaCategory, - ] - ); - - const inserterTabsContents = useMemo( - () => ( { - blocks: blocksTab, - patterns: patternsTab, - media: mediaTab, - } ), - [ blocksTab, mediaTab, patternsTab ] - ); - - const searchRef = useRef(); - useImperativeHandle( ref, () => ( { - focusSearch: () => { - searchRef.current.focus(); - }, - } ) ); + > + { showMediaPanel && ( + + ) } + + ); + }, [ + destinationRootClientId, + onInsert, + selectedMediaCategory, + setSelectedMediaCategory, + showMediaPanel, + ] ); - const showPatternPanel = - selectedTab === 'patterns' && - ! delayedFilterValue && - selectedPatternCategory; - const showAsTabs = ! delayedFilterValue && ( showPatterns || showMedia ); - const showMediaPanel = - selectedTab === 'media' && - ! delayedFilterValue && - selectedMediaCategory; + // When the pattern panel is showing, we want to use zoom out mode + useZoomOut( showPatternPanel ); const handleSetSelectedTab = ( value ) => { // If no longer on patterns tab remove the category setting. @@ -223,64 +273,37 @@ function InserterMenu( setSelectedTab( value ); }; + // Focus first active tab, if any + const tabsRef = useRef(); + useLayoutEffect( () => { + if ( tabsRef.current ) { + window.requestAnimationFrame( () => { + tabsRef.current + .querySelector( '[role="tab"][aria-selected="true"]' ) + ?.focus(); + } ); + } + }, [] ); + return ( -
-
- { - if ( hoveredItem ) setHoveredItem( null ); - setFilterValue( value ); - } } - value={ filterValue } - label={ __( 'Search for blocks and patterns' ) } - placeholder={ __( 'Search' ) } - ref={ searchRef } - /> - { !! delayedFilterValue && ( -
- -
- ) } - { showAsTabs && ( - - ) } - { ! delayedFilterValue && ! showAsTabs && ( -
- { blocksTab } -
- ) } +
+
+ + { inserterSearch } + { selectedTab === 'blocks' && + ! delayedFilterValue && + blocksTab } + { selectedTab === 'patterns' && + ! delayedFilterValue && + patternsTab } + { selectedTab === 'media' && mediaTab } +
- { showMediaPanel && ( - - ) } { showInserterHelpPanel && hoveredItem && ( ) } - { showPatternPanel && ( - - ) }
); } diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index eaacf32ca956a..3405ac98b881c 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -96,7 +96,7 @@ export default function QuickInserter( { return (
button { + display: block; + } + } - .block-editor-inserter__media-list__item-preview-options > button { + .block-editor-inserter__media-list__item-preview-options { + position: absolute; + right: $grid-unit-10; + top: $grid-unit-10; + + > button { + background: $white; + border-radius: $radius-block-ui; + display: none; + + // These styles are important so as focus isn't lost + // when the button is focused and we hover away. + &.is-opened, + &:focus { display: block; } - } - - .block-editor-inserter__media-list__item-preview-options { - position: absolute; - right: $grid-unit-10; - top: $grid-unit-10; - > button { - background: $white; - border-radius: $radius-block-ui; - display: none; - - // These styles are important so as focus isn't lost - // when the button is focused and we hover away. - &.is-opened, - &:focus { - display: block; - } - - &:hover { - box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + &:hover { + box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; } } } +} - .block-editor-inserter__media-list__item { - height: 100%; +.block-editor-inserter__media-list__item { + height: 100%; - .block-editor-inserter__media-list__item-preview { - display: flex; - align-items: center; - overflow: hidden; - border-radius: 2px; + .block-editor-inserter__media-list__item-preview { + display: flex; + align-items: center; + overflow: hidden; + border-radius: 2px; - > * { - margin: 0 auto; - max-width: 100%; - } + > * { + margin: 0 auto; + max-width: 100%; + } - .block-editor-inserter__media-list__item-preview-spinner { - display: flex; - height: 100%; - width: 100%; - position: absolute; - justify-content: center; - background: rgba($white, 0.7); - align-items: center; - pointer-events: none; - } + .block-editor-inserter__media-list__item-preview-spinner { + display: flex; + height: 100%; + width: 100%; + position: absolute; + justify-content: center; + background: rgba($white, 0.7); + align-items: center; + pointer-events: none; } + } - &:focus .block-editor-inserter__media-list__item-preview { - box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + &:focus .block-editor-inserter__media-list__item-preview { + box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; } } @@ -758,16 +695,23 @@ $block-inserter-tabs-height: 44px; } } +// Only relevant in zoom-out-mode +.block-editor-inserter__pattern-panel-placeholder { + display: none; +} + .is-zoom-out { .block-editor-inserter__menu { display: flex; } - .block-editor-inserter__patterns-category-dialog { - position: static; - } - - .block-editor-inserter__media-dialog { - position: static; + .show-panel::after { + // Makes space for the inserter flyout panel + @include break-medium { + content: ""; + display: block; + width: 300px; + height: 100%; + } } } diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index 4795c3ce4fdc2..5506e3c315cbd 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -3,6 +3,7 @@ */ import { privateApis as componentsPrivateApis } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies @@ -28,24 +29,19 @@ const mediaTab = { title: __( 'Media' ), }; -function InserterTabs( { - showPatterns = false, - showMedia = false, - onSelect, - tabsContents, -} ) { - const tabs = [ - blocksTab, - showPatterns && patternsTab, - showMedia && mediaTab, - ].filter( Boolean ); +function InserterTabs( { onSelect, children }, ref ) { + const tabs = [ blocksTab, patternsTab, mediaTab ]; return ( -
+
- + { tabs.map( ( tab ) => ( - + { tab.title } ) ) } @@ -55,8 +51,9 @@ function InserterTabs( { key={ tab.name } tabId={ tab.name } focusable={ false } + className="block-editor-inserter__tabpanel" > - { tabsContents[ tab.name ] } + { children } ) ) } @@ -64,4 +61,4 @@ function InserterTabs( { ); } -export default InserterTabs; +export default forwardRef( InserterTabs ); 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 a57add244cc76..8150c2d60027e 100644 --- a/packages/block-editor/src/components/line-height-control/index.js +++ b/packages/block-editor/src/components/line-height-control/index.js @@ -28,7 +28,9 @@ const LineHeightControl = ( { const adjustNextValue = ( nextValue, wasTypedOrPasted ) => { // Set the next value without modification if lineHeight has been defined. - if ( isDefined ) return nextValue; + if ( isDefined ) { + return nextValue; + } /** * The following logic handles the initial spin up/down action @@ -47,7 +49,9 @@ const LineHeightControl = ( { case '0': { // This means the user explicitly input '0', rather than using the // spin down action from an undefined value state. - if ( wasTypedOrPasted ) return nextValue; + if ( wasTypedOrPasted ) { + return nextValue; + } // Decrement by spin value. return BASE_DEFAULT_VALUE - spin; } diff --git a/packages/block-editor/src/components/line-height-control/test/index.js b/packages/block-editor/src/components/line-height-control/test/index.js index 606ee5627fb3b..7a101219f2f82 100644 --- a/packages/block-editor/src/components/line-height-control/test/index.js +++ b/packages/block-editor/src/components/line-height-control/test/index.js @@ -1,13 +1,13 @@ /** * External dependencies */ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * WordPress dependencies */ import { useState } from '@wordpress/element'; -import { UP, DOWN } from '@wordpress/keycodes'; /** * Internal dependencies @@ -29,35 +29,37 @@ const ControlledLineHeightControl = () => { }; describe( 'LineHeightControl', () => { - it( 'should immediately step up from the default value if up-arrowed from an unset state', () => { + it( 'should immediately step up from the default value if up-arrowed from an unset state', async () => { + const user = userEvent.setup(); render( ); const input = screen.getByRole( 'spinbutton' ); - act( () => input.focus() ); - fireEvent.keyDown( input, { keyCode: UP } ); + await user.click( input ); + await user.keyboard( '{ArrowUp}' ); expect( input ).toHaveValue( BASE_DEFAULT_VALUE + SPIN ); } ); - it( 'should immediately step down from the default value if down-arrowed from an unset state', () => { + it( 'should immediately step down from the default value if down-arrowed from an unset state', async () => { + const user = userEvent.setup(); render( ); const input = screen.getByRole( 'spinbutton' ); - act( () => input.focus() ); - fireEvent.keyDown( input, { keyCode: DOWN } ); + await user.click( input ); + await user.keyboard( '{ArrowDown}' ); expect( input ).toHaveValue( BASE_DEFAULT_VALUE - SPIN ); } ); - it( 'should immediately step up from the default value if spin button up was clicked from an unset state', () => { + it( 'should immediately step up from the default value if spin button up was clicked from an unset state', async () => { + const user = userEvent.setup(); render( ); const input = screen.getByRole( 'spinbutton' ); - act( () => input.focus() ); - fireEvent.change( input, { target: { value: 0.1 } } ); // simulates click on spin button up + await user.click( screen.getByRole( 'button', { name: 'Increment' } ) ); expect( input ).toHaveValue( BASE_DEFAULT_VALUE + SPIN ); } ); - it( 'should immediately step down from the default value if spin button down was clicked from an unset state', () => { + it( 'should immediately step down from the default value if spin button down was clicked from an unset state', async () => { + const user = userEvent.setup(); render( ); const input = screen.getByRole( 'spinbutton' ); - act( () => input.focus() ); - fireEvent.change( input, { target: { value: 0 } } ); // simulates click on spin button down + await user.click( screen.getByRole( 'button', { name: 'Decrement' } ) ); expect( input ).toHaveValue( BASE_DEFAULT_VALUE - SPIN ); } ); } ); diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index a0df6041bb31a..053839c86996d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -359,7 +359,7 @@ function LinkControl( { { isEditing && ( <>
( v ) => { * @return {string} the processed url to display. */ function getURLForDisplay( url ) { - if ( ! url ) return url; + if ( ! url ) { + return url; + } return pipe( safeDecodeURI, diff --git a/packages/block-editor/src/components/link-control/search-results.js b/packages/block-editor/src/components/link-control/search-results.js index 4d5a388a32afb..2186b5d8b62f7 100644 --- a/packages/block-editor/src/components/link-control/search-results.js +++ b/packages/block-editor/src/components/link-control/search-results.js @@ -7,7 +7,7 @@ import { VisuallyHidden, MenuGroup } from '@wordpress/components'; /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * Internal dependencies @@ -30,7 +30,7 @@ export default function LinkControlSearchResults( { createSuggestionButtonText, suggestionsQuery, } ) { - const resultsListClasses = classnames( + const resultsListClasses = clsx( 'block-editor-link-control__search-results', { 'is-loading': isLoading, diff --git a/packages/block-editor/src/components/list-view/block-contents.js b/packages/block-editor/src/components/list-view/block-contents.js index 0537a4b48cbe4..f6abaee9258d1 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -53,7 +53,7 @@ const ListViewBlockContents = forwardRef( const isBlockMoveTarget = blockMovingClientId && selectedBlockInBlockEditor === clientId; - const className = classnames( 'block-editor-list-view-block-contents', { + const className = clsx( 'block-editor-list-view-block-contents', { 'is-dropping-before': isBlockMoveTarget, } ); diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js index e52fc693afa79..101fb527b03b4 100644 --- a/packages/block-editor/src/components/list-view/block-select-button.js +++ b/packages/block-editor/src/components/list-view/block-select-button.js @@ -1,12 +1,11 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies */ -import { hasBlockSupport } from '@wordpress/blocks'; import { Button, __experimentalHStack as HStack, @@ -15,11 +14,8 @@ import { } from '@wordpress/components'; import { forwardRef } from '@wordpress/element'; import { Icon, lockSmall as lock, pinSmall } from '@wordpress/icons'; -import { SPACE, ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; +import { SPACE, ENTER } from '@wordpress/keycodes'; import { __, sprintf } from '@wordpress/i18n'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -29,9 +25,7 @@ import useBlockDisplayInformation from '../use-block-display-information'; import useBlockDisplayTitle from '../block-title/use-block-display-title'; import ListViewExpander from './expander'; import { useBlockLock } from '../block-lock'; -import { store as blockEditorStore } from '../../store'; import useListViewImages from './use-list-view-images'; -import { useListViewContext } from './context'; function ListViewBlockSelectButton( { @@ -48,7 +42,6 @@ function ListViewBlockSelectButton( draggable, isExpanded, ariaDescribedBy, - updateFocusAndSelection, }, ref ) { @@ -58,27 +51,8 @@ function ListViewBlockSelectButton( context: 'list-view', } ); const { isLocked } = useBlockLock( clientId ); - const { - canInsertBlockType, - getSelectedBlockClientIds, - getPreviousBlockClientId, - getBlockRootClientId, - getBlockOrder, - getBlockParents, - getBlocksByClientId, - canRemoveBlocks, - } = useSelect( blockEditorStore ); - const { - duplicateBlocks, - multiSelect, - removeBlocks, - insertAfterBlock, - insertBeforeBlock, - } = useDispatch( blockEditorStore ); - const isMatch = useShortcutEventMatch(); const isSticky = blockInformation?.positionType === 'sticky'; const images = useListViewImages( { clientId, isExpanded } ); - const { collapseAll, expand, rootClientId } = useListViewContext(); const positionLabel = blockInformation?.positionLabel ? sprintf( @@ -97,256 +71,89 @@ function ListViewBlockSelectButton( onDragStart?.( event ); }; - // Determine which blocks to update: - // If the current (focused) block is part of the block selection, use the whole selection. - // If the focused block is not part of the block selection, only update the focused block. - function getBlocksToUpdate() { - const selectedBlockClientIds = getSelectedBlockClientIds(); - const isUpdatingSelectedBlocks = - selectedBlockClientIds.includes( clientId ); - const firstBlockClientId = isUpdatingSelectedBlocks - ? selectedBlockClientIds[ 0 ] - : clientId; - const firstBlockRootClientId = - getBlockRootClientId( firstBlockClientId ); - - const blocksToUpdate = isUpdatingSelectedBlocks - ? selectedBlockClientIds - : [ clientId ]; - - return { - blocksToUpdate, - firstBlockClientId, - firstBlockRootClientId, - selectedBlockClientIds, - }; - } - /** * @param {KeyboardEvent} event */ - async function onKeyDownHandler( event ) { + function onKeyDown( event ) { if ( event.keyCode === ENTER || event.keyCode === SPACE ) { onClick( event ); - } else if ( - event.keyCode === BACKSPACE || - event.keyCode === DELETE || - isMatch( 'core/block-editor/remove', event ) - ) { - const { - blocksToUpdate: blocksToDelete, - firstBlockClientId, - firstBlockRootClientId, - selectedBlockClientIds, - } = getBlocksToUpdate(); - - // Don't update the selection if the blocks cannot be deleted. - if ( ! canRemoveBlocks( blocksToDelete, firstBlockRootClientId ) ) { - return; - } - - let blockToFocus = - getPreviousBlockClientId( firstBlockClientId ) ?? - // If the previous block is not found (when the first block is deleted), - // fallback to focus the parent block. - firstBlockRootClientId; - - removeBlocks( blocksToDelete, false ); - - // Update the selection if the original selection has been removed. - const shouldUpdateSelection = - selectedBlockClientIds.length > 0 && - getSelectedBlockClientIds().length === 0; - - // If there's no previous block nor parent block, focus the first block. - if ( ! blockToFocus ) { - blockToFocus = getBlockOrder()[ 0 ]; - } - - updateFocusAndSelection( blockToFocus, shouldUpdateSelection ); - } else if ( isMatch( 'core/block-editor/duplicate', event ) ) { - if ( event.defaultPrevented ) { - return; - } - event.preventDefault(); - - const { blocksToUpdate, firstBlockRootClientId } = - getBlocksToUpdate(); - - const canDuplicate = getBlocksByClientId( blocksToUpdate ).every( - ( block ) => { - return ( - !! block && - hasBlockSupport( block.name, 'multiple', true ) && - canInsertBlockType( block.name, firstBlockRootClientId ) - ); - } - ); - - if ( canDuplicate ) { - const updatedBlocks = await duplicateBlocks( - blocksToUpdate, - false - ); - - if ( updatedBlocks?.length ) { - // If blocks have been duplicated, focus the first duplicated block. - updateFocusAndSelection( updatedBlocks[ 0 ], false ); - } - } - } else if ( isMatch( 'core/block-editor/insert-before', event ) ) { - if ( event.defaultPrevented ) { - return; - } - event.preventDefault(); - - const { blocksToUpdate } = getBlocksToUpdate(); - await insertBeforeBlock( blocksToUpdate[ 0 ] ); - const newlySelectedBlocks = getSelectedBlockClientIds(); - - // Focus the first block of the newly inserted blocks, to keep focus within the list view. - updateFocusAndSelection( newlySelectedBlocks[ 0 ], false ); - } else if ( isMatch( 'core/block-editor/insert-after', event ) ) { - if ( event.defaultPrevented ) { - return; - } - event.preventDefault(); - - const { blocksToUpdate } = getBlocksToUpdate(); - await insertAfterBlock( blocksToUpdate.at( -1 ) ); - const newlySelectedBlocks = getSelectedBlockClientIds(); - - // Focus the first block of the newly inserted blocks, to keep focus within the list view. - updateFocusAndSelection( newlySelectedBlocks[ 0 ], false ); - } else if ( isMatch( 'core/block-editor/select-all', event ) ) { - if ( event.defaultPrevented ) { - return; - } - event.preventDefault(); - - const { firstBlockRootClientId, selectedBlockClientIds } = - getBlocksToUpdate(); - const blockClientIds = getBlockOrder( firstBlockRootClientId ); - if ( ! blockClientIds.length ) { - return; - } - - // If we have selected all sibling nested blocks, try selecting up a level. - // This is a similar implementation to that used by `useSelectAll`. - // `isShallowEqual` is used for the list view instead of a length check, - // as the array of siblings of the currently focused block may be a different - // set of blocks from the current block selection if the user is focused - // on a different part of the list view from the block selection. - if ( isShallowEqual( selectedBlockClientIds, blockClientIds ) ) { - // Only select up a level if the first block is not the root block. - // This ensures that the block selection can't break out of the root block - // used by the list view, if the list view is only showing a partial hierarchy. - if ( - firstBlockRootClientId && - firstBlockRootClientId !== rootClientId - ) { - updateFocusAndSelection( firstBlockRootClientId, true ); - return; - } - } - - // Select all while passing `null` to skip focusing to the editor canvas, - // and retain focus within the list view. - multiSelect( - blockClientIds[ 0 ], - blockClientIds[ blockClientIds.length - 1 ], - null - ); - } else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) { - if ( event.defaultPrevented ) { - return; - } - event.preventDefault(); - const { firstBlockClientId } = getBlocksToUpdate(); - const blockParents = getBlockParents( firstBlockClientId, false ); - // Collapse all blocks. - collapseAll(); - // Expand all parents of the current block. - expand( blockParents ); } } return ( - <> - - + { blockInformation.anchor } + + + ) } + { positionLabel && isSticky && ( + + + + ) } + { images.length ? ( + + { images.map( ( image, index ) => ( + + ) ) } + + ) : null } + { isLocked && ( + + + + ) } + + ); } diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 930d0a0f80ef6..cdbc5939e6a2a 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -22,7 +22,9 @@ import { } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { ESCAPE } from '@wordpress/keycodes'; +import { BACKSPACE, DELETE } from '@wordpress/keycodes'; +import isShallowEqual from '@wordpress/is-shallow-equal'; +import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -44,6 +46,7 @@ import { store as blockEditorStore } from '../../store'; import useBlockDisplayInformation from '../use-block-display-information'; import { useBlockLock } from '../block-lock'; import AriaReferencedText from './aria-referenced-text'; +import { unlock } from '../../lock-unlock'; function ListViewBlock( { block: { clientId }, @@ -78,29 +81,49 @@ function ListViewBlock( { isSelected && selectedClientIds[ selectedClientIds.length - 1 ] === clientId; - const { toggleBlockHighlight } = useDispatch( blockEditorStore ); - - const blockInformation = useBlockDisplayInformation( clientId ); + const { + toggleBlockHighlight, + duplicateBlocks, + multiSelect, + removeBlocks, + insertAfterBlock, + insertBeforeBlock, + setOpenedBlockSettingsMenu, + } = unlock( useDispatch( blockEditorStore ) ); - const { block, blockName, blockEditingMode } = useSelect( - ( select ) => { - const { getBlock, getBlockName, getBlockEditingMode } = - select( blockEditorStore ); + const { + canInsertBlockType, + getSelectedBlockClientIds, + getPreviousBlockClientId, + getBlockRootClientId, + getBlockOrder, + getBlockParents, + getBlocksByClientId, + canRemoveBlocks, + } = useSelect( blockEditorStore ); - return { - block: getBlock( clientId ), - blockName: getBlockName( clientId ), - blockEditingMode: getBlockEditingMode( clientId ), - }; - }, - [ clientId ] - ); + const blockInformation = useBlockDisplayInformation( clientId ); - const allowRightClickOverrides = useSelect( - ( select ) => - select( blockEditorStore ).getSettings().allowRightClickOverrides, - [] - ); + const { block, blockName, blockEditingMode, allowRightClickOverrides } = + useSelect( + ( select ) => { + const { + getBlock, + getBlockName, + getBlockEditingMode, + getSettings, + } = select( blockEditorStore ); + + return { + block: getBlock( clientId ), + blockName: getBlockName( clientId ), + blockEditingMode: getBlockEditingMode( clientId ), + allowRightClickOverrides: + getSettings().allowRightClickOverrides, + }; + }, + [ clientId ] + ); const showBlockActions = // When a block hides its toolbar it also hides the block settings menu, @@ -115,26 +138,194 @@ function ListViewBlock( { const { expand, collapse, + collapseAll, BlockSettingsMenu, listViewInstanceId, expandedState, setInsertedBlock, treeGridElementRef, + rootClientId, } = useListViewContext(); + const isMatch = useShortcutEventMatch(); + + // Determine which blocks to update: + // If the current (focused) block is part of the block selection, use the whole selection. + // If the focused block is not part of the block selection, only update the focused block. + function getBlocksToUpdate() { + const selectedBlockClientIds = getSelectedBlockClientIds(); + const isUpdatingSelectedBlocks = + selectedBlockClientIds.includes( clientId ); + const firstBlockClientId = isUpdatingSelectedBlocks + ? selectedBlockClientIds[ 0 ] + : clientId; + const firstBlockRootClientId = + getBlockRootClientId( firstBlockClientId ); + + const blocksToUpdate = isUpdatingSelectedBlocks + ? selectedBlockClientIds + : [ clientId ]; - // If multiple blocks are selected, deselect all blocks when the user - // presses the escape key. - const onKeyDown = ( event ) => { + return { + blocksToUpdate, + firstBlockClientId, + firstBlockRootClientId, + selectedBlockClientIds, + }; + } + + /** + * @param {KeyboardEvent} event + */ + async function onKeyDown( event ) { + if ( event.defaultPrevented ) { + return; + } + + // If multiple blocks are selected, deselect all blocks when the user + // presses the escape key. if ( - event.keyCode === ESCAPE && - ! event.defaultPrevented && + isMatch( 'core/block-editor/unselect', event ) && selectedClientIds.length > 0 ) { event.stopPropagation(); event.preventDefault(); selectBlock( event, undefined ); + } else if ( + event.keyCode === BACKSPACE || + event.keyCode === DELETE || + isMatch( 'core/block-editor/remove', event ) + ) { + const { + blocksToUpdate: blocksToDelete, + firstBlockClientId, + firstBlockRootClientId, + selectedBlockClientIds, + } = getBlocksToUpdate(); + + // Don't update the selection if the blocks cannot be deleted. + if ( ! canRemoveBlocks( blocksToDelete, firstBlockRootClientId ) ) { + return; + } + + let blockToFocus = + getPreviousBlockClientId( firstBlockClientId ) ?? + // If the previous block is not found (when the first block is deleted), + // fallback to focus the parent block. + firstBlockRootClientId; + + removeBlocks( blocksToDelete, false ); + + // Update the selection if the original selection has been removed. + const shouldUpdateSelection = + selectedBlockClientIds.length > 0 && + getSelectedBlockClientIds().length === 0; + + // If there's no previous block nor parent block, focus the first block. + if ( ! blockToFocus ) { + blockToFocus = getBlockOrder()[ 0 ]; + } + + updateFocusAndSelection( blockToFocus, shouldUpdateSelection ); + } else if ( isMatch( 'core/block-editor/duplicate', event ) ) { + event.preventDefault(); + + const { blocksToUpdate, firstBlockRootClientId } = + getBlocksToUpdate(); + + const canDuplicate = getBlocksByClientId( blocksToUpdate ).every( + ( blockToUpdate ) => { + return ( + !! blockToUpdate && + hasBlockSupport( + blockToUpdate.name, + 'multiple', + true + ) && + canInsertBlockType( + blockToUpdate.name, + firstBlockRootClientId + ) + ); + } + ); + + if ( canDuplicate ) { + const updatedBlocks = await duplicateBlocks( + blocksToUpdate, + false + ); + + if ( updatedBlocks?.length ) { + // If blocks have been duplicated, focus the first duplicated block. + updateFocusAndSelection( updatedBlocks[ 0 ], false ); + } + } + } else if ( isMatch( 'core/block-editor/insert-before', event ) ) { + event.preventDefault(); + + const { blocksToUpdate } = getBlocksToUpdate(); + await insertBeforeBlock( blocksToUpdate[ 0 ] ); + const newlySelectedBlocks = getSelectedBlockClientIds(); + + // Focus the first block of the newly inserted blocks, to keep focus within the list view. + setOpenedBlockSettingsMenu( undefined ); + updateFocusAndSelection( newlySelectedBlocks[ 0 ], false ); + } else if ( isMatch( 'core/block-editor/insert-after', event ) ) { + event.preventDefault(); + + const { blocksToUpdate } = getBlocksToUpdate(); + await insertAfterBlock( blocksToUpdate.at( -1 ) ); + const newlySelectedBlocks = getSelectedBlockClientIds(); + + // Focus the first block of the newly inserted blocks, to keep focus within the list view. + setOpenedBlockSettingsMenu( undefined ); + updateFocusAndSelection( newlySelectedBlocks[ 0 ], false ); + } else if ( isMatch( 'core/block-editor/select-all', event ) ) { + event.preventDefault(); + + const { firstBlockRootClientId, selectedBlockClientIds } = + getBlocksToUpdate(); + const blockClientIds = getBlockOrder( firstBlockRootClientId ); + if ( ! blockClientIds.length ) { + return; + } + + // If we have selected all sibling nested blocks, try selecting up a level. + // This is a similar implementation to that used by `useSelectAll`. + // `isShallowEqual` is used for the list view instead of a length check, + // as the array of siblings of the currently focused block may be a different + // set of blocks from the current block selection if the user is focused + // on a different part of the list view from the block selection. + if ( isShallowEqual( selectedBlockClientIds, blockClientIds ) ) { + // Only select up a level if the first block is not the root block. + // This ensures that the block selection can't break out of the root block + // used by the list view, if the list view is only showing a partial hierarchy. + if ( + firstBlockRootClientId && + firstBlockRootClientId !== rootClientId + ) { + updateFocusAndSelection( firstBlockRootClientId, true ); + return; + } + } + + // Select all while passing `null` to skip focusing to the editor canvas, + // and retain focus within the list view. + multiSelect( + blockClientIds[ 0 ], + blockClientIds[ blockClientIds.length - 1 ], + null + ); + } else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) { + event.preventDefault(); + const { firstBlockClientId } = getBlocksToUpdate(); + const blockParents = getBlockParents( firstBlockClientId, false ); + // Collapse all blocks. + collapseAll(); + // Expand all parents of the current block. + expand( blockParents ); } - }; + } const onMouseEnter = useCallback( () => { setIsHovered( true ); @@ -255,12 +446,12 @@ function ListViewBlock( { const hasSiblings = siblingBlockCount > 0; const hasRenderedMovers = showBlockMovers && hasSiblings; - const moverCellClassName = classnames( + const moverCellClassName = clsx( 'block-editor-list-view-block__mover-cell', { 'is-visible': isHovered || isSelected } ); - const listViewBlockSettingsClassName = classnames( + const listViewBlockSettingsClassName = clsx( 'block-editor-list-view-block__menu-cell', { 'is-visible': isHovered || isFirstSelectedBlock } ); @@ -272,7 +463,7 @@ function ListViewBlock( { colSpan = 3; } - const classes = classnames( { + const classes = clsx( { 'is-selected': isSelected, 'is-first-selected': isFirstSelectedBlock, 'is-last-selected': isLastSelectedBlock, @@ -345,7 +536,6 @@ function ListViewBlock( { isExpanded={ canEdit ? isExpanded : undefined } selectedClientIds={ selectedClientIds } ariaDescribedBy={ descriptionId } - updateFocusAndSelection={ updateFocusAndSelection } /> { `${ blockPositionDescription } ${ blockPropertiesDescription }` } 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 a590ebe029b27..3185bf9fbb248 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator.js +++ b/packages/block-editor/src/components/list-view/drop-indicator.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -302,7 +302,7 @@ export default function ListViewDropIndicatorPreview( { >
{ return state; }; -export const BLOCK_LIST_ITEM_HEIGHT = 36; +export const BLOCK_LIST_ITEM_HEIGHT = 32; /** @typedef {import('react').ComponentType} ComponentType */ /** @typedef {import('react').Ref} Ref */ @@ -366,7 +366,7 @@ function ListViewComponent( ) } 0 && blockDropTargetIndex !== undefined, diff --git a/packages/block-editor/src/components/list-view/leaf.js b/packages/block-editor/src/components/list-view/leaf.js index ba6d528746829..1e3e6320d2ace 100644 --- a/packages/block-editor/src/components/list-view/leaf.js +++ b/packages/block-editor/src/components/list-view/leaf.js @@ -2,7 +2,7 @@ * External dependencies */ import { animated } from '@react-spring/web'; -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -44,10 +44,7 @@ const ListViewLeaf = forwardRef( return ( = 0 { - margin-left: ($icon-size * $i) + 4 * ($i - 1); + margin-left: ($grid-unit-30 * $i); // Effectivly centers the expander below the parent's icon. } @else { - margin-left: ($icon-size * $i); + margin-left: 0; } } } @@ -540,7 +532,7 @@ svg { .block-editor-list-view-drop-indicator__line { background: rgba(var(--wp-admin-theme-color--rgb), 0.04); - height: 36px; + height: 32px; border-radius: 4px; overflow: hidden; } @@ -553,7 +545,7 @@ svg { .block-editor-list-view-placeholder { padding: 0; margin: 0; - height: 36px; + height: 32px; } .list-view-appender .block-editor-inserter__toggle { diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index 67908a43fc3c0..64730be6156df 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -62,7 +62,7 @@ import { store as blockEditorStore } from '../../store'; // When the indentation level, the corresponding left margin in `style.scss` // must be updated as well to ensure the drop zone is aligned with the indentation. -export const NESTING_LEVEL_INDENTATION = 28; +export const NESTING_LEVEL_INDENTATION = 24; /** * Determines whether the user is positioning the dragged block to be diff --git a/packages/block-editor/src/components/list-view/utils.js b/packages/block-editor/src/components/list-view/utils.js index dde1d39567f58..d6ca4a1f2e005 100644 --- a/packages/block-editor/src/components/list-view/utils.js +++ b/packages/block-editor/src/components/list-view/utils.js @@ -74,7 +74,9 @@ export function focusListItem( focusClientId, treeGridElement ) { const row = treeGridElement?.querySelector( `[role=row][data-block="${ focusClientId }"]` ); - if ( ! row ) return null; + if ( ! row ) { + return null; + } // Focus the first focusable in the row, which is the ListViewBlockSelectButton. return focus.focusable.find( row )[ 0 ]; }; diff --git a/packages/block-editor/src/components/media-placeholder/content.scss b/packages/block-editor/src/components/media-placeholder/content.scss index e8eaa4bf43d94..eeb2928df80ba 100644 --- a/packages/block-editor/src/components/media-placeholder/content.scss +++ b/packages/block-editor/src/components/media-placeholder/content.scss @@ -1,10 +1,3 @@ -.block-editor-media-placeholder__url-input-container { - // Reset the margin to ensure the url popover is adjacent to the button. - .block-editor-media-placeholder__button { - margin-bottom: 0; - } -} - .block-editor-media-placeholder__url-input-form { display: flex; @@ -29,10 +22,6 @@ flex-shrink: 1; } -.block-editor-media-placeholder__button { - margin-bottom: 0.5rem; -} - .block-editor-media-placeholder__cancel-button.is-link { margin: 1em; display: block; diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 41b1d3fe37c56..3391f72916167 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -81,7 +81,7 @@ const URLSelectionUI = ( { className="block-editor-media-placeholder__button" onClick={ openURLInput } isPressed={ isURLInputVisible } - variant="tertiary" + variant="secondary" > { __( 'Insert from URL' ) } @@ -349,7 +349,7 @@ export function MediaPlaceholder( { } } - const placeholderClassName = classnames( + const placeholderClassName = clsx( 'block-editor-media-placeholder', className, { @@ -422,7 +422,7 @@ export function MediaPlaceholder( { @@ -435,7 +435,7 @@ export function MediaPlaceholder( { const defaultButton = ( { open } ) => { return (
+ + ); +} diff --git a/packages/block-editor/src/components/writing-mode-control/style.scss b/packages/block-editor/src/components/segmented-text-control/style.scss similarity index 58% rename from packages/block-editor/src/components/writing-mode-control/style.scss rename to packages/block-editor/src/components/segmented-text-control/style.scss index 4b865dc0282c0..7a4a3bbea7cb3 100644 --- a/packages/block-editor/src/components/writing-mode-control/style.scss +++ b/packages/block-editor/src/components/segmented-text-control/style.scss @@ -1,18 +1,15 @@ -.block-editor-writing-mode-control { +.block-editor-segmented-text-control { border: 0; margin: 0; padding: 0; - .block-editor-writing-mode-control__buttons { + .block-editor-segmented-text-control__buttons { // 4px of padding makes the row 40px high, same as an input. padding: $grid-unit-05 0; display: flex; } .components-button.has-icon { - height: $grid-unit-40; margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; } } diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js index 3a549bd8e15cc..6feb583c29cdb 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js @@ -92,6 +92,8 @@ export default function SpacingInputControl( { ! isValueSpacingPreset( value ) ); + const [ minValue, setMinValue ] = useState( minimumCustomValue ); + const previousValue = usePrevious( value ); if ( !! value && @@ -222,13 +224,26 @@ export default function SpacingInputControl( { } value={ currentValue } units={ units } - min={ minimumCustomValue } + min={ minValue } placeholder={ allPlaceholder } disableUnits={ isMixed } label={ ariaLabel } hideLabelFromVision className="spacing-sizes-control__custom-value-input" size={ '__unstable-large' } + onDragStart={ () => { + if ( value?.charAt( 0 ) === '-' ) { + setMinValue( 0 ); + } + } } + onDrag={ () => { + if ( value?.charAt( 0 ) === '-' ) { + setMinValue( 0 ); + } + } } + onDragEnd={ () => { + setMinValue( minimumCustomValue ); + } } /> + TEXT_ALIGNMENT_OPTIONS.filter( ( option ) => + options.includes( option.value ) + ), + [ options ] + ); + + if ( ! validOptions.length ) { + return null; + } + + return ( + { + onChange( newValue === value ? undefined : newValue ); + } } + /> + ); +} diff --git a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js new file mode 100644 index 0000000000000..b2c171497acb0 --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TextAlignmentControl from '../'; + +export default { + title: 'BlockEditor/TextAlignmentControl', + component: TextAlignmentControl, + argTypes: { + onChange: { action: 'onChange' }, + className: { control: 'text' }, + options: { + control: 'check', + options: [ 'left', 'center', 'right', 'justify' ], + }, + value: { control: { type: null } }, + }, +}; + +const Template = ( { onChange, ...args } ) => { + const [ value, setValue ] = useState(); + return ( + { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/text-decoration-control/index.js b/packages/block-editor/src/components/text-decoration-control/index.js index 973fb71a7448f..d06632afdbb3c 100644 --- a/packages/block-editor/src/components/text-decoration-control/index.js +++ b/packages/block-editor/src/components/text-decoration-control/index.js @@ -1,28 +1,32 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { reset, formatStrikethrough, formatUnderline } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const TEXT_DECORATIONS = [ { - name: __( 'None' ), + label: __( 'None' ), value: 'none', icon: reset, }, { - name: __( 'Underline' ), + label: __( 'Underline' ), value: 'underline', icon: formatUnderline, }, { - name: __( 'Strikethrough' ), + label: __( 'Strikethrough' ), value: 'line-through', icon: formatStrikethrough, }, @@ -31,10 +35,10 @@ const TEXT_DECORATIONS = [ /** * Control to facilitate text decoration selections. * - * @param {Object} props Component props. - * @param {string} props.value Currently selected text decoration. - * @param {Function} props.onChange Handles change in text decoration selection. - * @param {string} [props.className] Additional class name to apply. + * @param {Object} props Component props. + * @param {string} props.value Currently selected text decoration. + * @param {Function} props.onChange Handles change in text decoration selection. + * @param {string} props.className Additional class name to apply. * * @return {Element} Text decoration control. */ @@ -44,34 +48,17 @@ export default function TextDecorationControl( { className, } ) { return ( -
- - { __( 'Decoration' ) } - -
- { TEXT_DECORATIONS.map( ( textDecoration ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/components/text-decoration-control/style.scss b/packages/block-editor/src/components/text-decoration-control/style.scss deleted file mode 100644 index 689707a66b7ca..0000000000000 --- a/packages/block-editor/src/components/text-decoration-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-decoration-control { - border: 0; - margin: 0; - padding: 0; - - .block-editor-text-decoration-control__buttons { - // 4px of padding makes the row 40px high, same as an input. - padding: $grid-unit-05 0; - display: flex; - } - - .components-button.has-icon { - height: $grid-unit-40; - margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; - } -} diff --git a/packages/block-editor/src/components/text-transform-control/index.js b/packages/block-editor/src/components/text-transform-control/index.js index c5d0ce37e9acd..f448a55ed946c 100644 --- a/packages/block-editor/src/components/text-transform-control/index.js +++ b/packages/block-editor/src/components/text-transform-control/index.js @@ -1,12 +1,11 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { reset, @@ -15,24 +14,29 @@ import { formatUppercase, } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const TEXT_TRANSFORMS = [ { - name: __( 'None' ), + label: __( 'None' ), value: 'none', icon: reset, }, { - name: __( 'Uppercase' ), + label: __( 'Uppercase' ), value: 'uppercase', icon: formatUppercase, }, { - name: __( 'Lowercase' ), + label: __( 'Lowercase' ), value: 'lowercase', icon: formatLowercase, }, { - name: __( 'Capitalize' ), + label: __( 'Capitalize' ), value: 'capitalize', icon: formatCapitalize, }, @@ -50,34 +54,17 @@ const TEXT_TRANSFORMS = [ */ export default function TextTransformControl( { className, value, onChange } ) { return ( -
- - { __( 'Letter case' ) } - -
- { TEXT_TRANSFORMS.map( ( textTransform ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/components/text-transform-control/style.scss b/packages/block-editor/src/components/text-transform-control/style.scss deleted file mode 100644 index 0e097405e332b..0000000000000 --- a/packages/block-editor/src/components/text-transform-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-transform-control { - border: 0; - margin: 0; - padding: 0; - - .block-editor-text-transform-control__buttons { - // 4px of padding makes the row 40px high, same as an input. - padding: $grid-unit-05 0; - display: flex; - } - - .components-button.has-icon { - height: $grid-unit-40; - margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; - } -} diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 56426d2eaf479..36e279f94cc79 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -441,7 +441,7 @@ class URLInput extends Component { const controlProps = { id: inputId, // Passes attribute to label for the for attribute label, - className: classnames( 'block-editor-url-input', className, { + className: clsx( 'block-editor-url-input', className, { 'is-full-width': isFullWidth, } ), hideLabelFromVision, @@ -546,7 +546,7 @@ class URLInput extends Component {
{ - if ( ! clientId ) return null; + if ( ! clientId ) { + return null; + } const { getBlockName, getBlockAttributes } = select( blockEditorStore ); const { getBlockType, getActiveBlockVariation } = select( blocksStore ); const blockName = getBlockName( clientId ); const blockType = getBlockType( blockName ); - if ( ! blockType ) return null; + if ( ! blockType ) { + return null; + } const attributes = getBlockAttributes( clientId ); const match = getActiveBlockVariation( blockName, attributes ); const isSynced = @@ -95,7 +99,9 @@ export default function useBlockDisplayInformation( clientId ) { positionType: attributes?.style?.position?.type, name: attributes?.metadata?.name, }; - if ( ! match ) return blockTypeInfo; + if ( ! match ) { + return blockTypeInfo; + } return { isSynced, diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index 58ce615436d8e..420cd398edfa3 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -252,7 +252,9 @@ export default function useOnBlockDrop( initialPosition = 0, clientIdsToReplace = [] ) => { - if ( ! Array.isArray( blocks ) ) blocks = [ blocks ]; + if ( ! Array.isArray( blocks ) ) { + blocks = [ blocks ]; + } const clientIds = getBlockOrder( targetRootClientId ); const clientId = clientIds[ targetBlockIndex ]; diff --git a/packages/block-editor/src/components/warning/index.js b/packages/block-editor/src/components/warning/index.js index 914f04e4fc80e..8b6279bef6044 100644 --- a/packages/block-editor/src/components/warning/index.js +++ b/packages/block-editor/src/components/warning/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -14,7 +14,7 @@ import { moreVertical } from '@wordpress/icons'; function Warning( { className, actions, children, secondaryActions } ) { return (
-
+

{ children } diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index c76ab74b03b77..7e6b36b0e2214 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classNames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -78,7 +78,7 @@ function WritingFlow( { children, ...props }, forwardedRef ) {

{ + const transformation = findTransform( + fromTransforms, + ( transform ) => + transform.type === 'files' && + transform.isMatch( [ file ] ) + ); + if ( transformation ) { + accumulator.push( + transformation.transform( [ file ] ) + ); + } + return accumulator; + }, [] ) + .flat(); + } else { + blocks = pasteHandler( { + HTML: html, + plainText, + mode: isFullySelected ? 'BLOCKS' : 'AUTO', + canUserUseUnfilteredHTML, + } ); + } - if ( selectedBlockClientIds.length === 1 ) { - const [ selectedBlockClientId ] = selectedBlockClientIds; + // Inline paste: let rich text handle it. + if ( typeof blocks === 'string' ) { + return; + } - if ( - blocks.every( ( block ) => - canInsertBlockType( - block.name, - selectedBlockClientId - ) - ) - ) { - insertBlocks( - blocks, - undefined, - selectedBlockClientId - ); - return; - } + if ( isFullySelected ) { + replaceBlocks( + selectedBlockClientIds, + blocks, + blocks.length - 1, + -1 + ); + event.preventDefault(); + return; } - replaceBlocks( - selectedBlockClientIds, - blocks, - blocks.length - 1, - -1 + // If a block doesn't support splitting, let rich text paste + // inline. + if ( + ! hasMultiSelection() && + ! hasBlockSupport( + getBlockName( selectedBlockClientIds[ 0 ] ), + 'splitting', + false + ) && + ! event.__deprecatedOnSplit + ) { + return; + } + + const [ firstSelectedClientId ] = selectedBlockClientIds; + const rootClientId = getBlockRootClientId( + firstSelectedClientId ); + + if ( + ! blocks.every( ( block ) => + canInsertBlockType( block.name, rootClientId ) + ) + ) { + return; + } + + __unstableSplitSelection( blocks ); + event.preventDefault(); } } diff --git a/packages/block-editor/src/components/writing-flow/use-drag-selection.js b/packages/block-editor/src/components/writing-flow/use-drag-selection.js index 4160565927ce2..1569c45a7c676 100644 --- a/packages/block-editor/src/components/writing-flow/use-drag-selection.js +++ b/packages/block-editor/src/components/writing-flow/use-drag-selection.js @@ -18,7 +18,9 @@ import { store as blockEditorStore } from '../../store'; function setContentEditableWrapper( node, value ) { node.contentEditable = value; // Firefox doesn't automatically move focus. - if ( value ) node.focus(); + if ( value ) { + node.focus(); + } } /** diff --git a/packages/block-editor/src/components/writing-flow/use-input.js b/packages/block-editor/src/components/writing-flow/use-input.js index f9baf216885ec..0f10cc9c2d1c7 100644 --- a/packages/block-editor/src/components/writing-flow/use-input.js +++ b/packages/block-editor/src/components/writing-flow/use-input.js @@ -4,7 +4,13 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; -import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; +import { + createBlock, + getDefaultBlockName, + hasBlockSupport, + getBlockTransforms, + findTransform, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -18,8 +24,15 @@ export default function useInput() { const { __unstableIsFullySelected, getSelectedBlockClientIds, + getSelectedBlockClientId, __unstableIsSelectionMergeable, hasMultiSelection, + getBlockName, + canInsertBlockType, + getBlockRootClientId, + getSelectionStart, + getSelectionEnd, + getBlockAttributes, } = useSelect( blockEditorStore ); const { replaceBlocks, @@ -27,6 +40,7 @@ export default function useInput() { removeBlocks, __unstableDeleteSelection, __unstableExpandSelection, + __unstableMarkAutomaticChange, } = useDispatch( blockEditorStore ); return useRefEffect( ( node ) => { @@ -45,6 +59,66 @@ export default function useInput() { } if ( ! hasMultiSelection() ) { + if ( event.keyCode === ENTER ) { + if ( event.shiftKey || __unstableIsFullySelected() ) { + return; + } + + const clientId = getSelectedBlockClientId(); + const blockName = getBlockName( clientId ); + const selectionStart = getSelectionStart(); + const selectionEnd = getSelectionEnd(); + + if ( + selectionStart.attributeKey === + selectionEnd.attributeKey + ) { + const selectedAttributeValue = + getBlockAttributes( clientId )[ + selectionStart.attributeKey + ]; + const transforms = getBlockTransforms( 'from' ).filter( + ( { type } ) => type === 'enter' + ); + const transformation = findTransform( + transforms, + ( item ) => { + return item.regExp.test( + selectedAttributeValue + ); + } + ); + + if ( transformation ) { + replaceBlocks( + clientId, + transformation.transform( { + content: selectedAttributeValue, + } ) + ); + __unstableMarkAutomaticChange(); + return; + } + } + + if ( + ! hasBlockSupport( blockName, 'splitting', false ) && + ! event.__deprecatedOnSplit + ) { + return; + } + + // Ensure template is not locked. + if ( + canInsertBlockType( + blockName, + getBlockRootClientId( clientId ) + ) + ) { + __unstableSplitSelection(); + event.preventDefault(); + } + } return; } diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index bfc64dde07153..818a2cdbbda02 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -118,7 +118,9 @@ export default function useTabNav() { // do it again here because after clearing block selection, // focus land on the writing flow container and pressing Tab // will no longer send focus through the focus capture element. - if ( event.target === node ) setNavigationMode( true ); + if ( event.target === node ) { + setNavigationMode( true ); + } return; } diff --git a/packages/block-editor/src/components/writing-mode-control/index.js b/packages/block-editor/src/components/writing-mode-control/index.js index 2adf8be14ad39..7732f54b1569a 100644 --- a/packages/block-editor/src/components/writing-mode-control/index.js +++ b/packages/block-editor/src/components/writing-mode-control/index.js @@ -1,23 +1,27 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { __, isRTL } from '@wordpress/i18n'; import { textHorizontal, textVertical } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const WRITING_MODES = [ { - name: __( 'Horizontal' ), + label: __( 'Horizontal' ), value: 'horizontal-tb', icon: textHorizontal, }, { - name: __( 'Vertical' ), + label: __( 'Vertical' ), value: isRTL() ? 'vertical-lr' : 'vertical-rl', icon: textVertical, }, @@ -35,34 +39,14 @@ const WRITING_MODES = [ */ export default function WritingModeControl( { className, value, onChange } ) { return ( -
- - { __( 'Orientation' ) } - -
- { WRITING_MODES.map( ( writingMode ) => { - return ( -
-
+ { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index 3e4a49bb38555..faaf5bb7f03ce 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -199,7 +199,7 @@ export function addAssignedAlign( props, blockType, attributes ) { hasWideBlockSupport ).includes( align ); if ( isAlignValid ) { - props.className = classnames( `align${ align }`, props.className ); + props.className = clsx( `align${ align }`, props.className ); } return props; diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index 3d47f42645afc..3e23efcbf5fc9 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -59,7 +59,7 @@ export function setBackgroundStyleDefaults( backgroundStyle ) { let backgroundStylesWithDefaults; // Set block background defaults. - if ( backgroundImage?.source === 'file' && !! backgroundImage?.url ) { + if ( !! backgroundImage?.url ) { if ( ! backgroundStyle?.backgroundSize ) { backgroundStylesWithDefaults = { backgroundSize: 'cover', diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index a8ed9bccaf7df..4ab4c69a41f31 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -320,7 +320,7 @@ function addSaveProps( props, blockNameOrType, attributes ) { } const borderClasses = getBorderClasses( attributes ); - const newClassName = classnames( props.className, borderClasses ); + const newClassName = clsx( props.className, borderClasses ); // If we are clearing the last of the previous classes in `className` // set it to `undefined` to avoid rendering empty DOM attributes. @@ -341,7 +341,7 @@ export function getBorderClasses( attributes ) { const { borderColor, style } = attributes; const borderColorClass = getColorClassName( 'border-color', borderColor ); - return classnames( { + return clsx( { 'has-border-color': borderColor || style?.border?.color, [ borderColorClass ]: !! borderColorClass, } ); diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index 0995f877309cc..ef8984c936785 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -174,23 +174,16 @@ export function addSaveProps( props, blockNameOrType, attributes ) { style?.color?.background || ( hasGradient && ( gradient || style?.color?.gradient ) ); - const newClassName = classnames( - props.className, - textClass, - gradientClass, - { - // Don't apply the background class if there's a custom gradient. - [ backgroundClass ]: - ( ! hasGradient || ! style?.color?.gradient ) && - !! backgroundClass, - 'has-text-color': - shouldSerialize( 'text' ) && - ( textColor || style?.color?.text ), - 'has-background': serializeHasBackground && hasBackground, - 'has-link-color': - shouldSerialize( 'link' ) && style?.elements?.link?.color, - } - ); + const newClassName = clsx( props.className, textClass, gradientClass, { + // Don't apply the background class if there's a custom gradient. + [ backgroundClass ]: + ( ! hasGradient || ! style?.color?.gradient ) && !! backgroundClass, + 'has-text-color': + shouldSerialize( 'text' ) && ( textColor || style?.color?.text ), + 'has-background': serializeHasBackground && hasBackground, + 'has-link-color': + shouldSerialize( 'link' ) && style?.elements?.link?.color, + } ); props.className = newClassName ? newClassName : undefined; return props; @@ -399,7 +392,7 @@ function useBlockProps( { return { ...saveProps, - className: classnames( + className: clsx( saveProps.className, // Add background image classes in the editor, if not already handled by background color values. ! hasBackgroundValue && getBackgroundImageClasses( style ) diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index 037fafe9ca840..91ef07506fd95 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -88,7 +88,7 @@ export function addSaveProps( extraProps, blockType, attributes ) { hasBlockSupport( blockType, 'customClassName', true ) && attributes.className ) { - extraProps.className = classnames( + extraProps.className = clsx( extraProps.className, attributes.className ); diff --git a/packages/block-editor/src/hooks/custom-class-name.native.js b/packages/block-editor/src/hooks/custom-class-name.native.js index 8d2b6560332e4..b3f153b2bdad1 100644 --- a/packages/block-editor/src/hooks/custom-class-name.native.js +++ b/packages/block-editor/src/hooks/custom-class-name.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -46,7 +46,7 @@ export function addSaveProps( extraProps, blockType, attributes ) { hasBlockSupport( blockType, 'customClassName', true ) && attributes.className ) { - extraProps.className = classnames( + extraProps.className = clsx( extraProps.className, attributes.className ); diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index f78a39230e89b..ffa4048b7740e 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -170,7 +170,7 @@ function useBlockProps( { name, minHeight, style } ) { return {}; } - const className = classnames( { + const className = clsx( { 'has-aspect-ratio': !! style?.dimensions?.aspectRatio, } ); diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 0df0d50d64457..cd3271ae50d5e 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -291,7 +291,9 @@ function useDuotoneStyles( { const blockElement = useBlockElement( clientId ); useEffect( () => { - if ( ! isValidFilter ) return; + if ( ! isValidFilter ) { + return; + } // Safari does not always update the duotone filter when the duotone colors // are changed. When using Safari, force the block element to be repainted by diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index ac6a381742c81..88192ddf5f055 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -389,7 +389,7 @@ function BlockWithLayoutStyles( { } ); // Attach a `wp-container-` id-based class name as well as a layout class name such as `is-layout-flex`. - const layoutClassNames = classnames( + const layoutClassNames = clsx( { [ `${ selectorPrefix }${ id }` ]: !! css, // Only attach a container class if there is generated CSS to be attached. }, diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 89b7a2d6b23b4..3c9cde478cdd0 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -351,7 +351,7 @@ function useBlockProps( { name, style } ) { } // Attach a `wp-container-` id-based class name. - const className = classnames( { + const className = clsx( { [ `wp-container-${ id }` ]: allowPositionStyles && !! css, // Only attach a container class if there is generated CSS to be attached. [ `is-position-${ style?.position?.type }` ]: allowPositionStyles && !! css && !! style?.position?.type, diff --git a/packages/block-editor/src/hooks/text-align.js b/packages/block-editor/src/hooks/text-align.js index daa8df6cc8210..7735a65ecc576 100644 --- a/packages/block-editor/src/hooks/text-align.js +++ b/packages/block-editor/src/hooks/text-align.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -136,7 +136,7 @@ function useBlockProps( { name, style } ) { const textAlign = style.typography.textAlign; - const className = classnames( { + const className = clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ); return { className }; @@ -169,7 +169,7 @@ export function addAssignedTextAlign( props, blockType, attributes ) { 'textAlign' ) ) { - props.className = classnames( + props.className = clsx( `has-text-align-${ textAlign }`, props.className ); diff --git a/packages/block-editor/src/hooks/use-color-props.js b/packages/block-editor/src/hooks/use-color-props.js index 679f0b7accbf7..5e52b3eb6841f 100644 --- a/packages/block-editor/src/hooks/use-color-props.js +++ b/packages/block-editor/src/hooks/use-color-props.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -51,7 +51,7 @@ export function getColorClassesAndStyles( attributes ) { const hasGradient = gradientClass || style?.color?.gradient; // Determine color CSS class name list. - const className = classnames( textClass, gradientClass, { + const className = clsx( textClass, gradientClass, { // Don't apply the background class if there's a gradient. [ backgroundClass ]: ! hasGradient && !! backgroundClass, 'has-text-color': textColor || style?.color?.text, diff --git a/packages/block-editor/src/hooks/use-typography-props.js b/packages/block-editor/src/hooks/use-typography-props.js index 3986bbf7cfe17..ec76cbf4bf6a0 100644 --- a/packages/block-editor/src/hooks/use-typography-props.js +++ b/packages/block-editor/src/hooks/use-typography-props.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -48,7 +48,7 @@ export function getTypographyClassesAndStyles( attributes, settings ) { const textAlignClassName = !! attributes?.style?.typography?.textAlign ? `has-text-align-${ attributes?.style?.typography?.textAlign }` : ''; - const className = classnames( + const className = clsx( fontFamilyClassName, textAlignClassName, getFontSizeClass( attributes?.fontSize ) diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index 84603c0161dd4..ce20cb5bd7a17 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -11,26 +11,36 @@ import { store as blockEditorStore } from '../store'; /** * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. + * + * @param {boolean} zoomOut If we should enter into zoomOut mode or not */ -export function useZoomOut() { +export function useZoomOut( zoomOut = true ) { const { __unstableSetEditorMode } = useDispatch( blockEditorStore ); - const { mode } = useSelect( ( select ) => { - return { - mode: select( blockEditorStore ).__unstableGetEditorMode(), + const { __unstableGetEditorMode } = useSelect( blockEditorStore ); + + const originalEditingMode = useRef( null ); + const mode = __unstableGetEditorMode(); + + useEffect( () => { + // Only set this on mount so we know what to return to when we unmount. + if ( ! originalEditingMode.current ) { + originalEditingMode.current = mode; + } + + return () => { + // We need to use __unstableGetEditorMode() here and not `mode`, as mode may not update on unmount + if ( __unstableGetEditorMode() !== originalEditingMode.current ) { + __unstableSetEditorMode( originalEditingMode.current ); + } }; }, [] ); - // Intentionality left without any dependency. - // This effect should only run when the component is rendered and unmounted. - // The effect opens the zoom-out view if it is not open before when applying a style variation. + // The effect opens the zoom-out view if we want it open and it's not currently in zoom-out mode. useEffect( () => { - if ( mode !== 'zoom-out' ) { + if ( zoomOut && mode !== 'zoom-out' ) { __unstableSetEditorMode( 'zoom-out' ); - return () => { - // Revert to original mode - __unstableSetEditorMode( mode ); - }; + } else if ( ! zoomOut && originalEditingMode.current !== mode ) { + __unstableSetEditorMode( originalEditingMode.current ); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); + }, [ __unstableSetEditorMode, zoomOut, mode ] ); } diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 2148b2bb8e5ce..391287afb6ba0 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -23,7 +23,7 @@ import { unlock } from '../lock-unlock'; /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * Removed falsy values from nested object. @@ -135,7 +135,13 @@ export function shouldSkipSerialization( const pendingStyleOverrides = new WeakMap(); -export function useStyleOverride( { id, css, assets, __unstableType } = {} ) { +export function useStyleOverride( { + id, + css, + assets, + __unstableType, + clientId, +} = {} ) { const { setStyleOverride, deleteStyleOverride } = unlock( useDispatch( blockEditorStore ) ); @@ -143,7 +149,9 @@ export function useStyleOverride( { id, css, assets, __unstableType } = {} ) { const fallbackId = useId(); useEffect( () => { // Unmount if there is CSS and assets are empty. - if ( ! css && ! assets ) return; + if ( ! css && ! assets ) { + return; + } const _id = id || fallbackId; const override = { @@ -151,6 +159,7 @@ export function useStyleOverride( { id, css, assets, __unstableType } = {} ) { css, assets, __unstableType, + clientId, }; // Batch updates to style overrides to avoid triggering cascading renders // for each style override block included in a tree and optimize initial render. @@ -187,6 +196,7 @@ export function useStyleOverride( { id, css, assets, __unstableType } = {} ) { }, [ id, css, + clientId, assets, __unstableType, fallbackId, @@ -213,6 +223,7 @@ export function useBlockSettings( name, parentLayout ) { customFontFamilies, defaultFontFamilies, themeFontFamilies, + defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, @@ -265,6 +276,7 @@ export function useBlockSettings( name, parentLayout ) { 'typography.fontFamilies.custom', 'typography.fontFamilies.default', 'typography.fontFamilies.theme', + 'typography.defaultFontSizes', 'typography.fontSizes.custom', 'typography.fontSizes.default', 'typography.fontSizes.theme', @@ -359,6 +371,7 @@ export function useBlockSettings( name, parentLayout ) { theme: themeFontSizes, }, customFontSize, + defaultFontSizes: defaultFontSizesEnabled, fontStyle, fontWeight, lineHeight, @@ -398,6 +411,7 @@ export function useBlockSettings( name, parentLayout ) { customFontFamilies, defaultFontFamilies, themeFontFamilies, + defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, @@ -596,7 +610,7 @@ export function createBlockListBlockFilter( features ) { return { ...acc, ...wrapperProps, - className: classnames( + className: clsx( acc.className, wrapperProps.className ), diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index b51c019e79178..f10fcc4df2c72 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -15,7 +15,7 @@ import { } from './components/inserter/search-items'; import { PrivateListView } from './components/list-view'; import BlockInfo from './components/block-info-slot-fill'; -import { useShowBlockTools } from './components/block-tools/use-show-block-tools'; +import { useHasBlockToolbar } from './components/block-toolbar/use-has-block-toolbar'; import { cleanEmptyObject, useStyleOverride } from './hooks/utils'; import BlockQuickNavigation from './components/block-quick-navigation'; import { LayoutStyle } from './components/block-list/layout'; @@ -23,6 +23,7 @@ import { BlockRemovalWarningModal } from './components/block-removal-warning-mod import { useLayoutClasses, useLayoutStyles } from './hooks'; import DimensionsTool from './components/dimensions-tool'; import ResolutionTool from './components/resolution-tool'; +import TextAlignmentControl from './components/text-alignment-control'; import { default as ReusableBlocksRenameHint, useReusableBlocksRenameHint, @@ -56,7 +57,7 @@ lock( privateApis, { PrivateListView, ResizableBoxPopover, BlockInfo, - useShowBlockTools, + useHasBlockToolbar, cleanEmptyObject, useStyleOverride, BlockQuickNavigation, @@ -66,6 +67,7 @@ lock( privateApis, { useLayoutStyles, DimensionsTool, ResolutionTool, + TextAlignmentControl, ReusableBlocksRenameHint, useReusableBlocksRenameHint, usesContextKey, diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index f98e4845e91f2..61acae16fcca2 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -14,6 +14,7 @@ import { synchronizeBlocksWithTemplate, getBlockSupport, isUnmodifiedDefaultBlock, + isUnmodifiedBlock, } from '@wordpress/blocks'; import { speak } from '@wordpress/a11y'; import { __, _n, sprintf } from '@wordpress/i18n'; @@ -25,6 +26,7 @@ import deprecated from '@wordpress/deprecated'; */ import { retrieveSelectedAttribute, + findRichTextAttributeKey, START_OF_SELECTED_AREA, } from '../utils/selection'; import { @@ -675,7 +677,9 @@ export const __unstableDeleteSelection = const selectionAnchor = select.getSelectionStart(); const selectionFocus = select.getSelectionEnd(); - if ( selectionAnchor.clientId === selectionFocus.clientId ) return; + if ( selectionAnchor.clientId === selectionFocus.clientId ) { + return; + } // It's not mergeable if there's no rich text selection. if ( @@ -683,8 +687,9 @@ export const __unstableDeleteSelection = ! selectionFocus.attributeKey || typeof selectionAnchor.offset === 'undefined' || typeof selectionFocus.offset === 'undefined' - ) + ) { return false; + } const anchorRootClientId = select.getBlockRootClientId( selectionAnchor.clientId @@ -818,24 +823,13 @@ export const __unstableDeleteSelection = /** * Split the current selection. + * @param {?Array} blocks */ export const __unstableSplitSelection = - () => - ( { select, dispatch } ) => { + ( blocks = [] ) => + ( { registry, select, dispatch } ) => { const selectionAnchor = select.getSelectionStart(); const selectionFocus = select.getSelectionEnd(); - - if ( selectionAnchor.clientId === selectionFocus.clientId ) return; - - // Can't split if the selection is not set. - if ( - ! selectionAnchor.attributeKey || - ! selectionFocus.attributeKey || - typeof selectionAnchor.offset === 'undefined' || - typeof selectionFocus.offset === 'undefined' - ) - return; - const anchorRootClientId = select.getBlockRootClientId( selectionAnchor.clientId ); @@ -866,12 +860,91 @@ export const __unstableSplitSelection = const selectionA = selectionStart; const selectionB = selectionEnd; - const blockA = select.getBlock( selectionA.clientId ); const blockB = select.getBlock( selectionB.clientId ); + const blockAType = getBlockType( blockA.name ); + const blockBType = getBlockType( blockB.name ); + const attributeKeyA = + typeof selectionA.attributeKey === 'string' + ? selectionA.attributeKey + : findRichTextAttributeKey( blockAType ); + const attributeKeyB = + typeof selectionB.attributeKey === 'string' + ? selectionB.attributeKey + : findRichTextAttributeKey( blockBType ); - const htmlA = blockA.attributes[ selectionA.attributeKey ]; - const htmlB = blockB.attributes[ selectionB.attributeKey ]; + // Can't split if the selection is not set. + if ( + ! attributeKeyA || + ! attributeKeyB || + typeof selectionAnchor.offset === 'undefined' || + typeof selectionFocus.offset === 'undefined' + ) { + return; + } + + // We can do some short-circuiting if the selection is collapsed. + if ( + selectionA.clientId === selectionB.clientId && + attributeKeyA === attributeKeyB && + selectionA.offset === selectionB.offset + ) { + // If an unmodified default block is selected, replace it. We don't + // want to be converting into a default block. + if ( blocks.length ) { + if ( isUnmodifiedDefaultBlock( blockA ) ) { + dispatch.replaceBlocks( + [ selectionA.clientId ], + blocks, + blocks.length - 1, + -1 + ); + return; + } + } + + // If selection is at the start or end, we can simply insert an + // empty block, provided this block has no inner blocks. + else if ( ! select.getBlockOrder( selectionA.clientId ).length ) { + function createEmpty() { + const defaultBlockName = getDefaultBlockName(); + return select.canInsertBlockType( + defaultBlockName, + anchorRootClientId + ) + ? createBlock( defaultBlockName ) + : createBlock( + select.getBlockName( selectionA.clientId ) + ); + } + + const length = select.getBlockAttributes( selectionA.clientId )[ + attributeKeyA + ].length; + + if ( selectionA.offset === 0 && length ) { + dispatch.insertBlocks( + [ createEmpty() ], + select.getBlockIndex( selectionA.clientId ), + anchorRootClientId, + false + ); + return; + } + + if ( selectionA.offset === length ) { + dispatch.insertBlocks( + [ createEmpty() ], + select.getBlockIndex( selectionA.clientId ) + 1, + anchorRootClientId + ); + return; + } + } + } + + const htmlA = blockA.attributes[ attributeKeyA ]; + const htmlB = blockB.attributes[ attributeKeyB ]; let valueA = create( { html: htmlA } ); let valueB = create( { html: htmlB } ); @@ -879,28 +952,127 @@ export const __unstableSplitSelection = valueA = remove( valueA, selectionA.offset, valueA.text.length ); valueB = remove( valueB, 0, selectionB.offset ); - dispatch.replaceBlocks( select.getSelectedBlockClientIds(), [ - { - // Preserve the original client ID. - ...blockA, - attributes: { - ...blockA.attributes, - [ selectionA.attributeKey ]: toHTMLString( { - value: valueA, - } ), - }, + let head = { + // Preserve the original client ID. + ...blockA, + // If both start and end are the same, should only copy innerBlocks + // once. + innerBlocks: + blockA.clientId === blockB.clientId ? [] : blockA.innerBlocks, + attributes: { + ...blockA.attributes, + [ attributeKeyA ]: toHTMLString( { value: valueA } ), }, - { - // Preserve the original client ID. - ...blockB, - attributes: { - ...blockB.attributes, - [ selectionB.attributeKey ]: toHTMLString( { - value: valueB, - } ), - }, + }; + + const tail = { + ...blockB, + // Only preserve the original client ID if the end is different. + clientId: + blockA.clientId === blockB.clientId + ? createBlock( blockB.name ).clientId + : blockB.clientId, + attributes: { + ...blockB.attributes, + [ attributeKeyB ]: toHTMLString( { value: valueB } ), }, - ] ); + }; + + if ( ! blocks.length ) { + dispatch.replaceBlocks( select.getSelectedBlockClientIds(), [ + head, + tail, + ] ); + return; + } + + let selection; + const output = []; + const clonedBlocks = [ ...blocks ]; + const firstBlock = clonedBlocks.shift(); + const headType = getBlockType( head.name ); + const firstBlocks = + headType.merge && firstBlock.name === headType.name + ? [ firstBlock ] + : switchToBlockType( firstBlock, headType.name ); + + if ( firstBlocks?.length ) { + const first = firstBlocks.shift(); + head = { + ...head, + attributes: headType.merge( head.attributes, first.attributes ), + }; + output.push( head ); + selection = { + clientId: head.clientId, + attributeKey: attributeKeyA, + offset: create( { html: head.attributes[ attributeKeyA ] } ) + .text.length, + }; + clonedBlocks.unshift( ...firstBlocks ); + } else { + if ( ! isUnmodifiedBlock( head ) ) { + output.push( head ); + } + output.push( firstBlock ); + } + + const lastBlock = clonedBlocks.pop(); + const tailType = getBlockType( tail.name ); + + if ( clonedBlocks.length ) { + output.push( ...clonedBlocks ); + } + + if ( lastBlock ) { + const lastBlocks = + tailType.merge && tailType.name === lastBlock.name + ? [ lastBlock ] + : switchToBlockType( lastBlock, tailType.name ); + + if ( lastBlocks?.length ) { + const last = lastBlocks.pop(); + output.push( { + ...tail, + attributes: tailType.merge( + last.attributes, + tail.attributes + ), + } ); + output.push( ...lastBlocks ); + selection = { + clientId: tail.clientId, + attributeKey: attributeKeyB, + offset: create( { + html: last.attributes[ attributeKeyB ], + } ).text.length, + }; + } else { + output.push( lastBlock ); + if ( ! isUnmodifiedBlock( tail ) ) { + output.push( tail ); + } + } + } else if ( ! isUnmodifiedBlock( tail ) ) { + output.push( tail ); + } + + registry.batch( () => { + dispatch.replaceBlocks( + select.getSelectedBlockClientIds(), + output, + output.length - 1, + 0 + ); + if ( selection ) { + dispatch.selectionChange( + selection.clientId, + selection.attributeKey, + selection.offset, + selection.offset + ); + } + } ); }; /** @@ -931,7 +1103,9 @@ export const mergeBlocks = const blockA = select.getBlock( clientIdA ); const blockAType = getBlockType( blockA.name ); - if ( ! blockAType ) return; + if ( ! blockAType ) { + return; + } const blockB = select.getBlock( clientIdB ); @@ -1438,26 +1612,42 @@ export const __unstableSetEditorMode = // When switching to zoom-out mode, we need to select the parent section if ( mode === 'zoom-out' ) { const firstSelectedClientId = select.getBlockSelectionStart(); + const allBlocks = select.getBlocks(); + const { sectionRootClientId } = unlock( registry.select( STORE_NAME ).getSettings() ); if ( sectionRootClientId ) { const sectionClientIds = select.getBlockOrder( sectionRootClientId ); + const lastSectionClientId = + sectionClientIds[ sectionClientIds.length - 1 ]; if ( sectionClientIds ) { - const parents = select.getBlockParents( - firstSelectedClientId - ); - const firstSectionClientId = parents.find( ( parent ) => - sectionClientIds.includes( parent ) - ); - dispatch.selectBlock( firstSectionClientId ); + if ( firstSelectedClientId ) { + const parents = select.getBlockParents( + firstSelectedClientId + ); + const firstSectionClientId = parents.find( ( parent ) => + sectionClientIds.includes( parent ) + ); + if ( firstSectionClientId ) { + dispatch.selectBlock( firstSectionClientId ); + } else { + dispatch.selectBlock( lastSectionClientId ); + } + } else { + dispatch.selectBlock( lastSectionClientId ); + } } } else if ( firstSelectedClientId ) { const rootClientId = select.getBlockHierarchyRootClientId( firstSelectedClientId ); dispatch.selectBlock( rootClientId ); + } else { + // If there's no block selected and no sectionRootClientId, select the last root block. + const lastRootBlock = allBlocks[ allBlocks.length - 1 ]; + dispatch.selectBlock( lastRootBlock?.clientId ); } } diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 94de85cbabe70..bb7f8fed18954 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -14,6 +14,7 @@ import { canInsertBlockType, getBlockName, getTemplateLock, + getClientIdsWithDescendants, } from './selectors'; import { checkAllowListRecursive, @@ -175,13 +176,36 @@ export function getOpenedBlockSettingsMenu( state ) { /** * Returns all style overrides, intended to be merged with global editor styles. * + * Overrides are sorted to match the order of the blocks they relate to. This + * is useful to maintain correct CSS cascade order. + * * @param {Object} state Global application state. * - * @return {Map} A map of style IDs to style overrides. + * @return {Array} An array of style ID to style override pairs. */ -export function getStyleOverrides( state ) { - return state.styleOverrides; -} +export const getStyleOverrides = createSelector( + ( state ) => { + const clientIds = getClientIdsWithDescendants( state ); + const clientIdMap = clientIds.reduce( ( acc, clientId, index ) => { + acc[ clientId ] = index; + return acc; + }, {} ); + + return [ ...state.styleOverrides ].sort( ( overrideA, overrideB ) => { + // Once the overrides Map is spread to an array, the first element + // is the key, while the second is the override itself including + // the clientId to sort by. + const [ , { clientId: clientIdA } ] = overrideA; + const [ , { clientId: clientIdB } ] = overrideB; + + const aIndex = clientIdMap[ clientIdA ] ?? -1; + const bIndex = clientIdMap[ clientIdB ] ?? -1; + + return aIndex - bIndex; + } ); + }, + ( state ) => [ state.blocks.order, state.styleOverrides ] +); /** @typedef {import('./actions').InserterMediaCategory} InserterMediaCategory */ /** diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 13024d4d2e8fa..3ea0fb4627304 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -304,20 +304,15 @@ const withBlockTree = action.blocks ); newState.tree = new Map( newState.tree ); - action.replacedClientIds - .concat( - // Controlled inner blocks are only removed - // if the block doesn't move to another position - // otherwise their content will be lost. - action.replacedClientIds - .filter( - ( clientId ) => ! inserterClientIds[ clientId ] - ) - .map( ( clientId ) => 'controlled||' + clientId ) - ) - .forEach( ( key ) => { - newState.tree.delete( key ); - } ); + action.replacedClientIds.forEach( ( clientId ) => { + newState.tree.delete( clientId ); + // Controlled inner blocks are only removed + // if the block doesn't move to another position + // otherwise their content will be lost. + if ( ! inserterClientIds[ clientId ] ) { + newState.tree.delete( 'controlled||' + clientId ); + } + } ); updateBlockTreeForBlocks( newState, action.blocks ); updateParentInnerBlocksInTree( @@ -358,15 +353,10 @@ const withBlockTree = } } newState.tree = new Map( newState.tree ); - action.removedClientIds - .concat( - action.removedClientIds.map( - ( clientId ) => 'controlled||' + clientId - ) - ) - .forEach( ( key ) => { - newState.tree.delete( key ); - } ); + action.removedClientIds.forEach( ( clientId ) => { + newState.tree.delete( clientId ); + newState.tree.delete( 'controlled||' + clientId ); + } ); updateParentInnerBlocksInTree( newState, parentsOfRemovedBlocks, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 19c609e848732..4ce85afb8cfd1 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1030,7 +1030,9 @@ export function __unstableIsSelectionMergeable( state, isForward ) { const selectionFocus = getSelectionEnd( state ); // It's not mergeable if the start and end are within the same block. - if ( selectionAnchor.clientId === selectionFocus.clientId ) return false; + if ( selectionAnchor.clientId === selectionFocus.clientId ) { + return false; + } // It's not mergeable if there's no rich text selection. if ( @@ -1038,8 +1040,9 @@ export function __unstableIsSelectionMergeable( state, isForward ) { ! selectionFocus.attributeKey || typeof selectionAnchor.offset === 'undefined' || typeof selectionFocus.offset === 'undefined' - ) + ) { return false; + } const anchorRootClientId = getBlockRootClientId( state, @@ -1081,12 +1084,16 @@ export function __unstableIsSelectionMergeable( state, isForward ) { const targetBlockName = getBlockName( state, targetBlockClientId ); const targetBlockType = getBlockType( targetBlockName ); - if ( ! targetBlockType.merge ) return false; + if ( ! targetBlockType.merge ) { + return false; + } const blockToMerge = getBlock( state, blockToMergeClientId ); // It's mergeable if the blocks are of the same type. - if ( blockToMerge.name === targetBlockName ) return true; + if ( blockToMerge.name === targetBlockName ) { + return true; + } // If the blocks are of a different type, try to transform the block being // merged into the same type of block. @@ -1938,7 +1945,9 @@ const buildBlockTypeItem = isDisabled, frecency: calculateFrecency( time, count ), }; - if ( buildScope === 'transform' ) return blockItemBase; + if ( buildScope === 'transform' ) { + return blockItemBase; + } const inserterVariations = getBlockVariations( blockType.name, @@ -2379,7 +2388,9 @@ export const __experimentalGetAllowedPatterns = createRegistrySelector( export const getPatternsByBlockTypes = createRegistrySelector( ( select ) => createSelector( ( state, blockNames, rootClientId = null ) => { - if ( ! blockNames ) return EMPTY_ARRAY; + if ( ! blockNames ) { + return EMPTY_ARRAY; + } const patterns = select( STORE_NAME ).__experimentalGetAllowedPatterns( rootClientId @@ -2438,7 +2449,9 @@ export const __experimentalGetPatternTransformItems = createRegistrySelector( ( select ) => createSelector( ( state, blocks, rootClientId = null ) => { - if ( ! blocks ) return EMPTY_ARRAY; + if ( ! blocks ) { + return EMPTY_ARRAY; + } /** * For now we only handle blocks without InnerBlocks and take into account * the `__experimentalRole` property of blocks' attributes for the transformation. diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 535d1005d274d..5080aa05718bb 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -41,9 +41,8 @@ @import "./components/multi-selection-inspector/style.scss"; @import "./components/responsive-block-control/style.scss"; @import "./components/rich-text/style.scss"; +@import "./components/segmented-text-control/style.scss"; @import "./components/skip-to-selected-block/style.scss"; -@import "./components/text-decoration-control/style.scss"; -@import "./components/text-transform-control/style.scss"; @import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss"; diff --git a/packages/block-editor/src/utils/object.js b/packages/block-editor/src/utils/object.js index 8f6c82a9c3991..c78fe0e656dfe 100644 --- a/packages/block-editor/src/utils/object.js +++ b/packages/block-editor/src/utils/object.js @@ -49,3 +49,19 @@ export const getValueFromObjectPath = ( object, path, defaultValue ) => { } ); return value ?? defaultValue; }; + +/** + * Helper util to filter out objects with duplicate values for a given property. + * + * @param {Object[]} array Array of objects to filter. + * @param {string} property Property to filter unique values by. + * + * @return {Object[]} Array of objects with unique values for the specified property. + */ +export function uniqByProperty( array, property ) { + const seen = new Set(); + return array.filter( ( item ) => { + const value = item[ property ]; + return seen.has( value ) ? false : seen.add( value ); + } ); +} diff --git a/packages/block-editor/src/utils/order-inserter-block-items.js b/packages/block-editor/src/utils/order-inserter-block-items.js index 696879b89db5e..aa2f1acd48c28 100644 --- a/packages/block-editor/src/utils/order-inserter-block-items.js +++ b/packages/block-editor/src/utils/order-inserter-block-items.js @@ -17,8 +17,12 @@ export const orderInserterBlockItems = ( items, priority ) => { let aIndex = priority.indexOf( aName ); let bIndex = priority.indexOf( bName ); // All other block items should come after that. - if ( aIndex < 0 ) aIndex = priority.length; - if ( bIndex < 0 ) bIndex = priority.length; + if ( aIndex < 0 ) { + aIndex = priority.length; + } + if ( bIndex < 0 ) { + bIndex = priority.length; + } return aIndex - bIndex; } ); diff --git a/packages/block-editor/src/utils/pasting.js b/packages/block-editor/src/utils/pasting.js index 3ce7c20083269..c106e78fe8465 100644 --- a/packages/block-editor/src/utils/pasting.js +++ b/packages/block-editor/src/utils/pasting.js @@ -112,7 +112,9 @@ export function shouldDismissPastedFiles( files, html /*, plainText */ ) { // other elements found, like
, but we assume that the user's // intention is to paste the actual image file. const IMAGE_TAG = /<\s*img\b/gi; - if ( html.match( IMAGE_TAG )?.length !== 1 ) return true; + if ( html.match( IMAGE_TAG )?.length !== 1 ) { + return true; + } // Even when there is exactly one tag in the HTML payload, we // choose to weed out local images, i.e. those whose source starts with @@ -121,7 +123,9 @@ export function shouldDismissPastedFiles( files, html /*, plainText */ ) { // text and exactly one image, and pasting that content using Google // Chrome. const IMG_WITH_LOCAL_SRC = /<\s*img\b[^>]*\bsrc="file:\/\//i; - if ( html.match( IMG_WITH_LOCAL_SRC ) ) return true; + if ( html.match( IMG_WITH_LOCAL_SRC ) ) { + return true; + } } return false; diff --git a/packages/block-editor/src/utils/selection.js b/packages/block-editor/src/utils/selection.js index 4e97148583879..57aab8cc0f55d 100644 --- a/packages/block-editor/src/utils/selection.js +++ b/packages/block-editor/src/utils/selection.js @@ -31,3 +31,11 @@ export function retrieveSelectedAttribute( blockAttributes ) { ); } ); } + +export function findRichTextAttributeKey( blockType ) { + for ( const [ key, value ] of Object.entries( blockType.attributes ) ) { + if ( value.source === 'rich-text' || value.source === 'html' ) { + return key; + } + } +} diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index 129a4cd0e3e7b..5de3cb7e592bc 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -5,7 +5,7 @@ import postcss, { CssSyntaxError } from 'postcss'; import wrap from 'postcss-prefixwrap'; import rebaseUrl from 'postcss-urlrebase'; -const transformStylesCache = new WeakMap(); +const cacheByWrapperSelector = new Map(); function transformStyle( { css, ignoredSelectors = [], baseURL }, @@ -65,14 +65,18 @@ function transformStyle( * @return {Array} converted rules. */ const transformStyles = ( styles, wrapperSelector = '' ) => { + let cache = cacheByWrapperSelector.get( wrapperSelector ); + if ( ! cache ) { + cache = new WeakMap(); + cacheByWrapperSelector.set( wrapperSelector, cache ); + } return styles.map( ( style ) => { - if ( transformStylesCache.has( style ) ) { - return transformStylesCache.get( style ); + let css = cache.get( style ); + if ( ! css ) { + css = transformStyle( style, wrapperSelector ); + cache.set( style, css ); } - - const transformedStyle = transformStyle( style, wrapperSelector ); - transformStylesCache.set( style, transformedStyle ); - return transformedStyle; + return css; } ); }; diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index c3153990e2f0e..4ca79d2c455d6 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Internal + +- Replaced `classnames` package with the faster and smaller `clsx` package ([#61138](https://github.com/WordPress/gutenberg/pull/61138)). + +## 8.34.0 (2024-05-02) + +## 8.33.0 (2024-04-19) + ## 8.32.0 (2024-04-03) ## 8.31.0 (2024-03-21) @@ -30,7 +38,6 @@ - Fix Image block lightbox missing alt attribute and improve accessibility. ([#54608](https://github.com/WordPress/gutenberg/pull/55010)) - ## 8.20.0 (2023-10-05) ## 8.19.0 (2023-09-20) @@ -184,8 +191,8 @@ ### Breaking Changes -- Drop support for Internet Explorer 11 ([#31110](https://github.com/WordPress/gutenberg/pull/31110)). Learn more at https://make.wordpress.org/core/2021/04/22/ie-11-support-phase-out-plan/. -- Increase the minimum Node.js version to v12 matching Long Term Support releases ([#31270](https://github.com/WordPress/gutenberg/pull/31270)). Learn more at https://nodejs.org/en/about/releases/. +- Drop support for Internet Explorer 11 ([#31110](https://github.com/WordPress/gutenberg/pull/31110)). Learn more at . +- Increase the minimum Node.js version to v12 matching Long Term Support releases ([#31270](https://github.com/WordPress/gutenberg/pull/31270)). Learn more at . ## 2.29.0 (2021-03-17) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index d9bb73400d79c..acb6ed307a331 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "8.32.0", + "version": "8.34.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -52,6 +52,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/interactivity": "file:../interactivity", "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/keycodes": "file:../keycodes", "@wordpress/notices": "file:../notices", "@wordpress/patterns": "file:../patterns", @@ -64,7 +65,7 @@ "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index e98f845cb56a7..0db2552b8ac26 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -107,7 +107,7 @@ function AudioEdit( { } ); } - const classes = classnames( className, { + const classes = clsx( className, { 'is-transient': isTemporaryAudio, } ); diff --git a/packages/block-library/src/avatar/edit.js b/packages/block-library/src/avatar/edit.js index 364baa39c441a..4fad0fb09f883 100644 --- a/packages/block-library/src/avatar/edit.js +++ b/packages/block-library/src/avatar/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -124,7 +124,7 @@ const ResizableAvatar = ( { { { + event.preventDefault(); + + const currentClientId = getSelectedBlockClientId(); + if ( currentClientId === null ) { + return; + } + + const blockName = getBlockName( currentClientId ); + const isParagraph = blockName === 'core/paragraph'; + const isHeading = blockName === 'core/heading'; + + if ( ! isParagraph && ! isHeading ) { + return; + } + + const destinationBlockName = + level === 0 ? 'core/paragraph' : 'core/heading'; + + const attributes = getBlockAttributes( currentClientId ); + + // Avoid unnecessary block transform when attempting to transform to + // the same block type and/or same level. + if ( + ( isParagraph && level === 0 ) || + ( isHeading && attributes.level === level ) + ) { + return; + } + + const textAlign = + blockName === 'core/paragraph' ? 'align' : 'textAlign'; + const destinationTextAlign = + destinationBlockName === 'core/paragraph' ? 'align' : 'textAlign'; + + replaceBlocks( + currentClientId, + createBlock( destinationBlockName, { + level, + content: attributes.content, + ...{ [ destinationTextAlign ]: attributes[ textAlign ] }, + } ) + ); + }; + + useEffect( () => { + registerShortcut( { + name: 'core/block-editor/transform-heading-to-paragraph', + category: 'block-library', + description: __( 'Transform heading to paragraph.' ), + keyCombination: { + modifier: 'access', + character: '0', + }, + aliases: [ + { + modifier: 'access', + character: '7', + }, + ], + } ); + + [ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => { + registerShortcut( { + name: `core/block-editor/transform-paragraph-to-heading-${ level }`, + category: 'block-library', + description: __( 'Transform paragraph to heading.' ), + keyCombination: { + modifier: 'access', + character: `${ level }`, + }, + } ); + } ); + }, [] ); + + useShortcut( + 'core/block-editor/transform-heading-to-paragraph', + ( event ) => handleTransformHeadingAndParagraph( event, 0 ) + ); + + [ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => { + //the loop is based off on a constant therefore + //the hook will execute the same way every time + //eslint-disable-next-line react-hooks/rules-of-hooks + useShortcut( + `core/block-editor/transform-paragraph-to-heading-${ level }`, + ( event ) => handleTransformHeadingAndParagraph( event, level ) + ); + } ); + + return null; +} + +export default BlockKeyboardShortcuts; diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 0d465e2d0fb61..a274843ff37ed 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -92,7 +92,9 @@ const useInferredLayout = ( blocks, parentLayout ) => { function hasOverridableBlocks( blocks ) { return blocks.some( ( block ) => { - if ( isOverridableBlock( block ) ) return true; + if ( isOverridableBlock( block ) ) { + return true; + } return hasOverridableBlocks( block.innerBlocks ); } ); } @@ -159,7 +161,9 @@ function getContentValuesFromInnerBlocks( blocks, defaultValues, legacyIdMap ) { /** @type {Record}>} */ const content = {}; for ( const block of blocks ) { - if ( block.name === patternBlockName ) continue; + if ( block.name === patternBlockName ) { + continue; + } if ( block.innerBlocks.length ) { Object.assign( content, @@ -261,8 +265,9 @@ function ReusableBlockEdit( { ); const isMissing = hasResolved && ! record; - // The initial value of the `content` attribute. - const initialContent = useRef( content ); + // The value of the `content` attribute, stored in a `ref` to avoid triggering the effect + // that runs `applyInitialContentValuesToInnerBlocks` unnecessarily. + const contentRef = useRef( content ); // The default content values from the original pattern for overridable attributes. // Set by the `applyInitialContentValuesToInnerBlocks` function. @@ -349,7 +354,7 @@ function ReusableBlockEdit( { // Build a map of clientIds to the old nano id system to provide back compat. legacyIdMap.current = getLegacyIdMap( initialBlocks, - initialContent.current + contentRef.current ); defaultContent.current = {}; const originalEditingMode = getBlockEditingMode( patternClientId ); @@ -360,7 +365,7 @@ function ReusableBlockEdit( { const blocks = hasPatternOverridesSource ? applyInitialContentValuesToInnerBlocks( initialBlocks, - initialContent.current, + contentRef.current, defaultContent.current, legacyIdMap.current ) @@ -389,7 +394,7 @@ function ReusableBlockEdit( { const layoutClasses = useLayoutClasses( { layout }, name ); const blockProps = useBlockProps( { - className: classnames( + className: clsx( 'block-library-block__reusable-block-container', layout && layoutClasses, { [ `align${ alignment }` ]: alignment } @@ -397,7 +402,7 @@ function ReusableBlockEdit( { } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { - templateLock: 'all', + templateLock: 'contentOnly', layout, renderAppender: innerBlocks?.length ? undefined @@ -417,13 +422,15 @@ function ReusableBlockEdit( { if ( blocks !== prevBlocks ) { prevBlocks = blocks; syncDerivedUpdates( () => { + const updatedContent = getContentValuesFromInnerBlocks( + blocks, + defaultContent.current, + legacyIdMap.current + ); setAttributes( { - content: getContentValuesFromInnerBlocks( - blocks, - defaultContent.current, - legacyIdMap.current - ), + content: updatedContent, } ); + contentRef.current = updatedContent; } ); } }, blockEditorStore ); diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index ec9f042cf5bcf..740f3e50f84ee 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -73,6 +73,7 @@ }, "supports": { "anchor": true, + "splitting": true, "align": false, "alignWide": false, "color": { diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index 1ec6fde3fe4a4..8ab83e1b09518 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -58,7 +58,7 @@ function migrateAlign( attributes ) { const { align, ...otherAttributes } = attributes; return { ...otherAttributes, - className: classnames( + className: clsx( otherAttributes.className, `align${ attributes.align }` ), @@ -226,7 +226,7 @@ const v11 = { const borderProps = getBorderClassesAndStyles( attributes ); const colorProps = getColorClassesAndStyles( attributes ); const spacingProps = getSpacingClassesAndStyles( attributes ); - const buttonClasses = classnames( + const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, borderProps.className, @@ -246,7 +246,7 @@ const v11 = { // if it had already been assigned, for the sake of backward-compatibility. // A title will no longer be assigned for new or updated button block links. - const wrapperClasses = classnames( className, { + const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, } ); @@ -352,7 +352,7 @@ const v10 = { const borderProps = getBorderClassesAndStyles( attributes ); const colorProps = getColorClassesAndStyles( attributes ); const spacingProps = getSpacingClassesAndStyles( attributes ); - const buttonClasses = classnames( + const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, borderProps.className, @@ -372,7 +372,7 @@ const v10 = { // if it had already been assigned, for the sake of backward-compatibility. // A title will no longer be assigned for new or updated button block links. - const wrapperClasses = classnames( className, { + const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, } ); @@ -468,7 +468,7 @@ const deprecated = [ const borderRadius = style?.border?.radius; const colorProps = getColorClassesAndStyles( attributes ); - const buttonClasses = classnames( + const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, { @@ -484,7 +484,7 @@ const deprecated = [ // if it had already been assigned, for the sake of backward-compatibility. // A title will no longer be assigned for new or updated button block links. - const wrapperClasses = classnames( className, { + const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, @@ -558,7 +558,7 @@ const deprecated = [ const { borderRadius, linkTarget, rel, text, title, url, width } = attributes; const colorProps = getColorClassesAndStyles( attributes ); - const buttonClasses = classnames( + const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, { @@ -574,7 +574,7 @@ const deprecated = [ // if it had already been assigned, for the sake of backward-compatibility. // A title will no longer be assigned for new or updated button block links. - const wrapperClasses = classnames( className, { + const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, } ); @@ -646,7 +646,7 @@ const deprecated = [ const { borderRadius, linkTarget, rel, text, title, url, width } = attributes; const colorProps = getColorClassesAndStyles( attributes ); - const buttonClasses = classnames( + const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, { @@ -662,7 +662,7 @@ const deprecated = [ // if it had already been assigned, for the sake of backward-compatibility. // A title will no longer be assigned for new or updated button block links. - const wrapperClasses = classnames( className, { + const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, } ); @@ -725,7 +725,7 @@ const deprecated = [ save( { attributes } ) { const { borderRadius, linkTarget, rel, text, title, url } = attributes; - const buttonClasses = classnames( 'wp-block-button__link', { + const buttonClasses = clsx( 'wp-block-button__link', { 'no-border-radius': borderRadius === 0, } ); const buttonStyle = { @@ -823,7 +823,7 @@ const deprecated = [ getColorClassName( 'background-color', backgroundColor ); const gradientClass = __experimentalGetGradientClass( gradient ); - const buttonClasses = classnames( 'wp-block-button__link', { + const buttonClasses = clsx( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, [ textClass ]: textClass, 'has-background': @@ -941,7 +941,7 @@ const deprecated = [ backgroundColor ); - const buttonClasses = classnames( 'wp-block-button__link', { + const buttonClasses = clsx( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, [ textClass ]: textClass, 'has-background': backgroundColor || customBackgroundColor, @@ -1009,7 +1009,7 @@ const deprecated = [ backgroundColor ); - const buttonClasses = classnames( 'wp-block-button__link', { + const buttonClasses = clsx( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, [ textClass ]: textClass, 'has-background': backgroundColor || customBackgroundColor, diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 24f82f1ba2f4f..45bc2b715e3ff 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * Internal dependencies @@ -257,7 +257,7 @@ function ButtonEdit( props ) { <>
- createBlock( 'core/button', { - ...attributes, - text: value, - } ) - } onReplace={ onReplace } onMerge={ mergeBlocks } identifier="text" diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index ba0fbd45f083c..65f095b21e144 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -42,7 +42,7 @@ export default function save( { attributes, className } ) { const colorProps = getColorClassesAndStyles( attributes ); const spacingProps = getSpacingClassesAndStyles( attributes ); const shadowProps = getShadowClassesAndStyles( attributes ); - const buttonClasses = classnames( + const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, borderProps.className, @@ -65,7 +65,7 @@ export default function save( { attributes, className } ) { // if it had already been assigned, for the sake of backward-compatibility. // A title will no longer be assigned for new or updated button block links. - const wrapperClasses = classnames( className, { + const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, } ); diff --git a/packages/block-library/src/buttons/deprecated.js b/packages/block-library/src/buttons/deprecated.js index 469cc14e76439..da28a6617fb65 100644 --- a/packages/block-library/src/buttons/deprecated.js +++ b/packages/block-library/src/buttons/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies */ @@ -63,7 +63,7 @@ const deprecated = [ return (
+ { + nextWidth = 0 > parseFloat( nextWidth ) ? '0' : nextWidth; + setAttributes( { width: nextWidth } ); + } } + units={ units } + /> + + ); +} + function ColumnEdit( { attributes: { verticalAlignment, width, templateLock, allowedBlocks }, setAttributes, clientId, } ) { - const classes = classnames( 'block-core-columns', { + const classes = clsx( 'block-core-columns', { [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); - - const [ availableUnits ] = useSettings( 'spacing.units' ); - const units = useCustomUnits( { - availableUnits: availableUnits || [ '%', 'px', 'em', 'rem', 'vw' ], - } ); - const { columnsIds, hasChildBlocks, rootClientId } = useSelect( ( select ) => { const { getBlockOrder, getBlockRootClientId } = @@ -103,20 +119,10 @@ function ColumnEdit( { /> - - { - nextWidth = - 0 > parseFloat( nextWidth ) ? '0' : nextWidth; - setAttributes( { width: nextWidth } ); - } } - units={ units } - /> - +
diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 871a12c7ee600..0a88d36beaf96 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -220,7 +220,9 @@ function ColumnEditWrapper( props ) { const { verticalAlignment } = props.attributes; const getVerticalAlignmentRemap = ( alignment ) => { - if ( ! alignment ) return styles.flexBase; + if ( ! alignment ) { + return styles.flexBase; + } return { ...styles.flexBase, ...styles[ `is-vertically-aligned-${ alignment }` ], diff --git a/packages/block-library/src/column/save.js b/packages/block-library/src/column/save.js index 9d52ff19d0823..87806f70f25ff 100644 --- a/packages/block-library/src/column/save.js +++ b/packages/block-library/src/column/save.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -11,7 +11,7 @@ import { useInnerBlocksProps, useBlockProps } from '@wordpress/block-editor'; export default function save( { attributes } ) { const { verticalAlignment, width } = attributes; - const wrapperClasses = classnames( { + const wrapperClasses = clsx( { [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); diff --git a/packages/block-library/src/columns/deprecated.js b/packages/block-library/src/columns/deprecated.js index be4527c5b1dab..21be6d29118ee 100644 --- a/packages/block-library/src/columns/deprecated.js +++ b/packages/block-library/src/columns/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -95,7 +95,7 @@ export default [ const textClass = getColorClassName( 'color', textColor ); - const className = classnames( { + const className = clsx( { 'has-background': backgroundColor || customBackgroundColor, 'has-text-color': textColor || customTextColor, [ backgroundClass ]: backgroundClass, @@ -210,7 +210,7 @@ export default [ save( { attributes } ) { const { verticalAlignment, columns } = attributes; - const wrapperClasses = classnames( `has-${ columns }-columns`, { + const wrapperClasses = clsx( `has-${ columns }-columns`, { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index d70b14ddcd2d7..f8cf0297302cc 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -44,8 +44,11 @@ const DEFAULT_BLOCK = { name: 'core/column', }; -function ColumnsEditContainer( { attributes, setAttributes, clientId } ) { - const { isStackedOnMobile, verticalAlignment, templateLock } = attributes; +function ColumnInspectorControls( { + clientId, + setAttributes, + isStackedOnMobile, +} ) { const { count, canInsertColumnBlock, minCount } = useSelect( ( select ) => { const { @@ -79,47 +82,8 @@ function ColumnsEditContainer( { attributes, setAttributes, clientId } ) { }, [ clientId ] ); - - const registry = useRegistry(); - const { getBlocks, getBlockOrder } = useSelect( blockEditorStore ); - const { updateBlockAttributes, replaceInnerBlocks } = - useDispatch( blockEditorStore ); - - const classes = classnames( { - [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, - [ `is-not-stacked-on-mobile` ]: ! isStackedOnMobile, - } ); - - const blockProps = useBlockProps( { - className: classes, - } ); - const innerBlocksProps = useInnerBlocksProps( blockProps, { - defaultBlock: DEFAULT_BLOCK, - directInsert: true, - orientation: 'horizontal', - renderAppender: false, - templateLock, - } ); - - /** - * Update all child Column blocks with a new vertical alignment setting - * based on whatever alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis. - * - * @param {string} newVerticalAlignment The vertical alignment setting. - */ - function updateAlignment( newVerticalAlignment ) { - const innerBlockClientIds = getBlockOrder( clientId ); - - // Update own and child Column block vertical alignments. - // This is a single action; the batching prevents creating multiple history records. - registry.batch( () => { - setAttributes( { verticalAlignment: newVerticalAlignment } ); - updateBlockAttributes( innerBlockClientIds, { - verticalAlignment: newVerticalAlignment, - } ); - } ); - } + const { getBlocks } = useSelect( blockEditorStore ); + const { replaceInnerBlocks } = useDispatch( blockEditorStore ); /** * Updates the column count, including necessary revisions to child Column @@ -184,6 +148,86 @@ function ColumnsEditContainer( { attributes, setAttributes, clientId } ) { replaceInnerBlocks( clientId, innerBlocks ); } + return ( + + { canInsertColumnBlock && ( + <> + + updateColumns( count, Math.max( minCount, value ) ) + } + min={ Math.max( 1, minCount ) } + max={ Math.max( 6, count ) } + /> + { count > 6 && ( + + { __( + 'This column count exceeds the recommended amount and may cause visual breakage.' + ) } + + ) } + + ) } + + setAttributes( { + isStackedOnMobile: ! isStackedOnMobile, + } ) + } + /> + + ); +} + +function ColumnsEditContainer( { attributes, setAttributes, clientId } ) { + const { isStackedOnMobile, verticalAlignment, templateLock } = attributes; + const registry = useRegistry(); + const { getBlockOrder } = useSelect( blockEditorStore ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + + const classes = clsx( { + [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + [ `is-not-stacked-on-mobile` ]: ! isStackedOnMobile, + } ); + + const blockProps = useBlockProps( { + className: classes, + } ); + const innerBlocksProps = useInnerBlocksProps( blockProps, { + defaultBlock: DEFAULT_BLOCK, + directInsert: true, + orientation: 'horizontal', + renderAppender: false, + templateLock, + } ); + + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} newVerticalAlignment The vertical alignment setting. + */ + function updateAlignment( newVerticalAlignment ) { + const innerBlockClientIds = getBlockOrder( clientId ); + + // Update own and child Column block vertical alignments. + // This is a single action; the batching prevents creating multiple history records. + registry.batch( () => { + setAttributes( { verticalAlignment: newVerticalAlignment } ); + updateBlockAttributes( innerBlockClientIds, { + verticalAlignment: newVerticalAlignment, + } ); + } ); + } + return ( <> @@ -193,46 +237,11 @@ function ColumnsEditContainer( { attributes, setAttributes, clientId } ) { /> - - { canInsertColumnBlock && ( - <> - - updateColumns( - count, - Math.max( minCount, value ) - ) - } - min={ Math.max( 1, minCount ) } - max={ Math.max( 6, count ) } - /> - { count > 6 && ( - - { __( - 'This column count exceeds the recommended amount and may cause visual breakage.' - ) } - - ) } - - ) } - - setAttributes( { - isStackedOnMobile: ! isStackedOnMobile, - } ) - } - /> - +
@@ -265,6 +274,7 @@ function Placeholder( { clientId, name, setAttributes } ) { icon={ blockType?.icon?.src } label={ blockType?.title } variations={ variations } + instructions={ __( 'Divide into columns. Select a layout:' ) } onSelect={ ( nextVariation = defaultVariation ) => { if ( nextVariation.attributes ) { setAttributes( nextVariation.attributes ); diff --git a/packages/block-library/src/columns/save.js b/packages/block-library/src/columns/save.js index 0dde559530853..cdd291f02c67a 100644 --- a/packages/block-library/src/columns/save.js +++ b/packages/block-library/src/columns/save.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -11,7 +11,7 @@ import { useInnerBlocksProps, useBlockProps } from '@wordpress/block-editor'; export default function save( { attributes } ) { const { isStackedOnMobile, verticalAlignment } = attributes; - const className = classnames( { + const className = clsx( { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, [ `is-not-stacked-on-mobile` ]: ! isStackedOnMobile, } ); diff --git a/packages/block-library/src/columns/variations.js b/packages/block-library/src/columns/variations.js index 440175584fcdd..044ba34b7b8fb 100644 --- a/packages/block-library/src/columns/variations.js +++ b/packages/block-library/src/columns/variations.js @@ -18,16 +18,12 @@ const variations = [ description: __( 'One column' ), icon: ( - + ), innerBlocks: [ [ 'core/column' ] ], @@ -39,16 +35,12 @@ const variations = [ description: __( 'Two columns; equal split' ), icon: ( - + ), isDefault: true, @@ -61,16 +53,12 @@ const variations = [ description: __( 'Two columns; one-third, two-thirds split' ), icon: ( - + ), innerBlocks: [ @@ -85,16 +73,12 @@ const variations = [ description: __( 'Two columns; two-thirds, one-third split' ), icon: ( - + ), innerBlocks: [ @@ -109,15 +93,12 @@ const variations = [ description: __( 'Three columns; equal split' ), icon: ( - + ), innerBlocks: [ @@ -133,15 +114,12 @@ const variations = [ description: __( 'Three columns; wide center column' ), icon: ( - + ), innerBlocks: [ diff --git a/packages/block-library/src/comment-author-name/edit.js b/packages/block-library/src/comment-author-name/edit.js index 629ff185394c5..b20816d39b64f 100644 --- a/packages/block-library/src/comment-author-name/edit.js +++ b/packages/block-library/src/comment-author-name/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -37,7 +37,7 @@ export default function Edit( { setAttributes, } ) { const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); diff --git a/packages/block-library/src/comment-content/edit.js b/packages/block-library/src/comment-content/edit.js index a22acd43612b2..45c2324dfa387 100644 --- a/packages/block-library/src/comment-content/edit.js +++ b/packages/block-library/src/comment-content/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -34,7 +34,7 @@ export default function Edit( { context: { commentId }, } ) { const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); diff --git a/packages/block-library/src/comment-edit-link/edit.js b/packages/block-library/src/comment-edit-link/edit.js index 72de15757560f..d3cde7f2abeaf 100644 --- a/packages/block-library/src/comment-edit-link/edit.js +++ b/packages/block-library/src/comment-edit-link/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -20,7 +20,7 @@ export default function Edit( { setAttributes, } ) { const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); diff --git a/packages/block-library/src/comment-reply-link/edit.js b/packages/block-library/src/comment-reply-link/edit.js index 08a4d21de88c7..a8ef53aaaa25c 100644 --- a/packages/block-library/src/comment-reply-link/edit.js +++ b/packages/block-library/src/comment-reply-link/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -25,7 +25,7 @@ import { */ function Edit( { setAttributes, attributes: { textAlign } } ) { const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); diff --git a/packages/block-library/src/comment-template/util.js b/packages/block-library/src/comment-template/util.js index 32aab0a568709..88fc666204922 100644 --- a/packages/block-library/src/comment-template/util.js +++ b/packages/block-library/src/comment-template/util.js @@ -30,7 +30,9 @@ export const convertToTree = ( data ) => { const table = {}; - if ( ! data ) return []; + if ( ! data ) { + return []; + } // First create a hash table of { [id]: { ...comment, children: [] }} data.forEach( ( item ) => { diff --git a/packages/block-library/src/comments-title/edit.js b/packages/block-library/src/comments-title/edit.js index 3765a45d4f8bb..07149607bfc39 100644 --- a/packages/block-library/src/comments-title/edit.js +++ b/packages/block-library/src/comments-title/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -32,7 +32,7 @@ export default function Edit( { const [ rawTitle ] = useEntityProp( 'postType', postType, 'title', postId ); const isSiteEditor = typeof postId === 'undefined'; const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); diff --git a/packages/block-library/src/comments/edit/comments-legacy.js b/packages/block-library/src/comments/edit/comments-legacy.js index 16ba4d8b1641e..be460dbbcb0b5 100644 --- a/packages/block-library/src/comments/edit/comments-legacy.js +++ b/packages/block-library/src/comments/edit/comments-legacy.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -38,7 +38,7 @@ export default function CommentsLegacy( { ]; const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index e2e773cace142..c5f430eead685 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -305,7 +305,7 @@ const v13 = { const backgroundPosition = mediaPosition( focalPoint ); - const classes = classnames( + const classes = clsx( { 'is-light': ! isDark, 'has-parallax': hasParallax, @@ -316,7 +316,7 @@ const v13 = { getPositionClassName( contentPosition ) ); - const imgClasses = classnames( + const imgClasses = clsx( 'wp-block-cover__image-background', id ? `wp-image-${ id }` : null, { @@ -331,7 +331,7 @@ const v13 = {
{ cannotEmbed && ( -
+
{ __( 'Sorry, this content could not be embedded.' ) }
- { ' ' } - -
+ + { ' ' } + + + ) } ); diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index 017fac72f6637..be74f2f627a10 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -6,7 +6,7 @@ import { getPhotoHtml } from './util'; /** * External dependencies */ -import classnames from 'classnames/dedupe'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -82,7 +82,7 @@ class EmbedPreview extends Component { __( 'Embedded content from %s' ), parsedHostBaseUrl ); - const sandboxClassnames = classnames( + const sandboxClassnames = clsx( type, className, 'wp-block-embed__wrapper' @@ -116,7 +116,7 @@ class EmbedPreview extends Component { return (
diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index f4baf97871059..d692b6cd58563 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import classnames from 'classnames/dedupe'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -86,7 +86,7 @@ const EmbedPreview = ( { __( 'Embedded content from %s' ), parsedHostBaseUrl ); - const sandboxClassnames = classnames( + const sandboxClassnames = clsx( type, className, 'wp-block-embed__wrapper' diff --git a/packages/block-library/src/embed/icons.js b/packages/block-library/src/embed/icons.js index 21a3b20ae278a..2e9386373f083 100644 --- a/packages/block-library/src/embed/icons.js +++ b/packages/block-library/src/embed/icons.js @@ -178,3 +178,12 @@ export const embedPocketCastsIcon = { ), }; + +export const embedBlueskyIcon = ( + + + +); diff --git a/packages/block-library/src/embed/save.js b/packages/block-library/src/embed/save.js index cb52b22b9b744..8f825d5cabc04 100644 --- a/packages/block-library/src/embed/save.js +++ b/packages/block-library/src/embed/save.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames/dedupe'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -19,7 +19,7 @@ export default function save( { attributes } ) { return null; } - const className = classnames( 'wp-block-embed', { + const className = clsx( 'wp-block-embed', { [ `is-type-${ type }` ]: type, [ `is-provider-${ providerNameSlug }` ]: providerNameSlug, [ `wp-block-embed-${ providerNameSlug }` ]: providerNameSlug, diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index c591c5d19e2d2..528dec45d1ecf 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames/dedupe'; +import clsx from 'clsx'; import memoize from 'memize'; /** @@ -97,7 +97,9 @@ export const createUpgradedEmbedBlock = ( const { preview, attributes = {} } = props; const { url, providerNameSlug, type, ...restAttributes } = attributes; - if ( ! url || ! getBlockType( DEFAULT_EMBED_BLOCK ) ) return; + if ( ! url || ! getBlockType( DEFAULT_EMBED_BLOCK ) ) { + return; + } const matchedBlock = findMoreSuitableBlock( url ); @@ -180,12 +182,16 @@ export const removeAspectRatioClasses = ( existingClassNames ) => { } const aspectRatioClassNames = ASPECT_RATIOS.reduce( ( accumulator, { className } ) => { - accumulator[ className ] = false; + accumulator.push( className ); return accumulator; }, - { 'wp-has-aspect-ratio': false } + [ 'wp-has-aspect-ratio' ] ); - return classnames( existingClassNames, aspectRatioClassNames ); + let outputClassNames = existingClassNames; + for ( const className of aspectRatioClassNames ) { + outputClassNames = outputClassNames.replace( className, '' ); + } + return outputClassNames.trim(); }; /** @@ -228,7 +234,7 @@ export function getClassNames( return removeAspectRatioClasses( existingClassNames ); } // Close aspect ratio match found. - return classnames( + return clsx( removeAspectRatioClasses( existingClassNames ), potentialRatio.className, 'wp-has-aspect-ratio' diff --git a/packages/block-library/src/embed/variations.js b/packages/block-library/src/embed/variations.js index 66cd266b060d9..e770c12ddfd97 100644 --- a/packages/block-library/src/embed/variations.js +++ b/packages/block-library/src/embed/variations.js @@ -27,6 +27,7 @@ import { embedPinterestIcon, embedWolframIcon, embedPocketCastsIcon, + embedBlueskyIcon, } from './icons'; /** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */ @@ -360,6 +361,14 @@ const variations = [ patterns: [ /^https?:\/\/(www\.)?wolframcloud\.com\/obj\/.+/i ], attributes: { providerNameSlug: 'wolfram-cloud', responsive: true }, }, + { + name: 'bluesky', + title: 'Bluesky', + icon: embedBlueskyIcon, + description: __( 'Embed a Bluesky post.' ), + patterns: [ /^https?:\/\/bsky\.app\/profile\/.+\/post\/.+/i ], + attributes: { providerNameSlug: 'bluesky' }, + }, ]; /** @@ -368,7 +377,9 @@ const variations = [ * Block by providing its attributes. */ variations.forEach( ( variation ) => { - if ( variation.isActive ) return; + if ( variation.isActive ) { + return; + } variation.isActive = ( blockAttributes, variationAttributes ) => blockAttributes.providerNameSlug === variationAttributes.providerNameSlug; diff --git a/packages/block-library/src/embed/wp-embed-preview.js b/packages/block-library/src/embed/wp-embed-preview.js index 1992310217aca..ad68bc3f7aadd 100644 --- a/packages/block-library/src/embed/wp-embed-preview.js +++ b/packages/block-library/src/embed/wp-embed-preview.js @@ -20,10 +20,14 @@ export default function WpEmbedPreview( { html } ) { const iframe = doc.querySelector( 'iframe' ); const iframeProps = {}; - if ( ! iframe ) return iframeProps; + if ( ! iframe ) { + return iframeProps; + } Array.from( iframe.attributes ).forEach( ( { name, value } ) => { - if ( name === 'style' ) return; + if ( name === 'style' ) { + return; + } iframeProps[ attributeMap[ name ] || name ] = value; } ); diff --git a/packages/block-library/src/file/deprecated.js b/packages/block-library/src/file/deprecated.js index a2dc67644fac9..51b3704205b38 100644 --- a/packages/block-library/src/file/deprecated.js +++ b/packages/block-library/src/file/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -127,7 +127,7 @@ const v3 = { { showDownloadButton && ( diff --git a/packages/block-library/src/form-input/edit.js b/packages/block-library/src/form-input/edit.js index 6b721c17fa552..97115d86fb370 100644 --- a/packages/block-library/src/form-input/edit.js +++ b/packages/block-library/src/form-input/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classNames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -83,7 +83,7 @@ function InputFieldBlock( { attributes, setAttributes, className } ) { { controls } { controls } @@ -120,7 +120,7 @@ function InputFieldBlock( { attributes, setAttributes, className } ) { /> { /* eslint-disable jsx-a11y/label-has-associated-control */ }
diff --git a/packages/block-library/src/gallery/deprecated.js b/packages/block-library/src/gallery/deprecated.js index 89df39d48ab1d..722890c0de013 100644 --- a/packages/block-library/src/gallery/deprecated.js +++ b/packages/block-library/src/gallery/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -228,7 +228,7 @@ const v7 = { save( { attributes } ) { const { caption, columns, imageCrop } = attributes; - const className = classnames( 'has-nested-images', { + const className = clsx( 'has-nested-images', { [ `columns-${ columns }` ]: columns !== undefined, [ `columns-default` ]: columns === undefined, 'is-cropped': imageCrop, @@ -1061,7 +1061,7 @@ const v1 = { imageCrop, linkTo, } = attributes; - const className = classnames( `columns-${ columns }`, { + const className = clsx( `columns-${ columns }`, { alignnone: align === 'none', 'is-cropped': imageCrop, } ); diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 2eff9a4f2143b..4cf315a847c84 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -488,7 +488,7 @@ function GalleryEdit( props ) { ); const blockProps = useBlockProps( { - className: classnames( className, 'has-nested-images' ), + className: clsx( className, 'has-nested-images' ), } ); const nativeInnerBlockProps = Platform.isNative && { diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js index 10c05eb8cc401..96b70cb87a70c 100644 --- a/packages/block-library/src/gallery/gallery.js +++ b/packages/block-library/src/gallery/gallery.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -32,7 +32,7 @@ export default function Gallery( props ) { return (
getInnerBlock( galleryBlock, 'Image', { rowIndex } ); diff --git a/packages/block-library/src/gallery/v1/gallery-image.js b/packages/block-library/src/gallery/v1/gallery-image.js index c9c2bf6cb5bd1..368d5da55c4ac 100644 --- a/packages/block-library/src/gallery/v1/gallery-image.js +++ b/packages/block-library/src/gallery/v1/gallery-image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -192,7 +192,7 @@ class GalleryImage extends Component { /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ ); - const className = classnames( { + const className = clsx( { 'is-selected': isSelected, 'is-transient': isBlobURL( url ), } ); diff --git a/packages/block-library/src/gallery/v1/gallery.js b/packages/block-library/src/gallery/v1/gallery.js index 7fc587f27911a..1af81bbc16b50 100644 --- a/packages/block-library/src/gallery/v1/gallery.js +++ b/packages/block-library/src/gallery/v1/gallery.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -48,7 +48,7 @@ export const Gallery = ( props ) => { return (
{ li { - margin: 0 $grid-unit-15 $grid-unit-15 $grid-unit-15; - width: auto; - display: flex; - flex-direction: column; - align-items: center; - } - .wp-block-group-placeholder__variations li > .wp-block-group-placeholder__variation-button { - width: 44px; - height: 32px; - padding: 0; - &:hover { - box-shadow: none; - } - } - .components-placeholder { - min-height: auto; - padding: $grid-unit-30; - align-items: center; - } - .is-small, - .is-medium { - .wp-block-group-placeholder__variations > li { - margin: $grid-unit-15; - } - } -} diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index 2d06f1a965c52..1ccfa4fa89c1a 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -22,14 +22,6 @@ export { metadata, name }; export const settings = { icon, example: { - attributes: { - style: { - color: { - text: '#000000', - background: '#ffffff', - }, - }, - }, innerBlocks: [ { name: 'core/paragraph', diff --git a/packages/block-library/src/group/placeholder.js b/packages/block-library/src/group/placeholder.js index 16a99a9287338..ed3e53c3eb2b9 100644 --- a/packages/block-library/src/group/placeholder.js +++ b/packages/block-library/src/group/placeholder.js @@ -20,42 +20,41 @@ const getGroupPlaceholderIcons = ( name = 'group' ) => { group: ( - + ), 'group-row': ( - + ), 'group-stack': ( - + ), 'group-grid': ( - - + ), }; @@ -159,11 +158,12 @@ function GroupPlaceHolder( { name, onSelect } ) { { variations.map( ( variation ) => (
  • diff --git a/packages/block-library/src/list-item/hooks/use-merge.js b/packages/block-library/src/list-item/hooks/use-merge.js index 2fbee4ba275a1..4b56abb320d92 100644 --- a/packages/block-library/src/list-item/hooks/use-merge.js +++ b/packages/block-library/src/list-item/hooks/use-merge.js @@ -35,8 +35,12 @@ export default function useMerge( clientId, onMerge ) { function getParentListItemId( id ) { const listId = getBlockRootClientId( id ); const parentListItemId = getBlockRootClientId( listId ); - if ( ! parentListItemId ) return; - if ( getBlockName( parentListItemId ) !== 'core/list-item' ) return; + if ( ! parentListItemId ) { + return; + } + if ( getBlockName( parentListItemId ) !== 'core/list-item' ) { + return; + } return parentListItemId; } @@ -49,9 +53,13 @@ export default function useMerge( clientId, onMerge ) { */ function _getNextId( id ) { const next = getNextBlockClientId( id ); - if ( next ) return next; + if ( next ) { + return next; + } const parentListItemId = getParentListItemId( id ); - if ( ! parentListItemId ) return; + if ( ! parentListItemId ) { + return; + } return _getNextId( parentListItemId ); } 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 a17890eada6c5..48bccc8f2cd4c 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 @@ -27,8 +27,12 @@ export default function useOutdentListItem() { function getParentListItemId( id ) { const listId = getBlockRootClientId( id ); const parentListItemId = getBlockRootClientId( listId ); - if ( ! parentListItemId ) return; - if ( getBlockName( parentListItemId ) !== 'core/list-item' ) return; + if ( ! parentListItemId ) { + return; + } + if ( getBlockName( parentListItemId ) !== 'core/list-item' ) { + return; + } return parentListItemId; } @@ -37,17 +41,23 @@ export default function useOutdentListItem() { clientIds = [ clientIds ]; } - if ( ! clientIds.length ) return; + if ( ! clientIds.length ) { + return; + } const firstClientId = clientIds[ 0 ]; // Can't outdent if it's not a list item. - if ( getBlockName( firstClientId ) !== 'core/list-item' ) return; + if ( getBlockName( firstClientId ) !== 'core/list-item' ) { + return; + } const parentListItemId = getParentListItemId( firstClientId ); // Can't outdent if it's at the top level. - if ( ! parentListItemId ) return; + if ( ! parentListItemId ) { + return; + } const parentListId = getBlockRootClientId( firstClientId ); const lastClientId = clientIds[ clientIds.length - 1 ]; diff --git a/packages/block-library/src/media-text/deprecated.js b/packages/block-library/src/media-text/deprecated.js index d03659022b95b..1cd1472f876c0 100644 --- a/packages/block-library/src/media-text/deprecated.js +++ b/packages/block-library/src/media-text/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -263,7 +263,7 @@ const v6 = { attributes.mediaSizeSlug || DEFAULT_MEDIA_SIZE_SLUG; const newRel = ! rel ? undefined : rel; - const imageClasses = classnames( { + const imageClasses = clsx( { [ `wp-image-${ mediaId }` ]: mediaId && mediaType === 'image', [ `size-${ mediaSizeSlug }` ]: mediaId && mediaType === 'image', } ); @@ -293,7 +293,7 @@ const v6 = { image: () => image, video: () =>
    { /* eslint-disable jsx-a11y/anchor-is-valid */ } diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index ffeea51996a02..5653e04fca88a 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -198,6 +198,13 @@ function render_block_core_navigation_link( $attributes, $content, $block ) { $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] ); $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind ); + if ( is_post_type_archive() ) { + $queried_archive_link = get_post_type_archive_link( get_queried_object()->name ); + if ( $attributes['url'] === $queried_archive_link ) { + $is_active = true; + } + } + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $css_classes . ' wp-block-navigation-item' . ( $has_submenu ? ' has-child' : '' ) . diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js index f5ae68695770c..ce79af40e4708 100644 --- a/packages/block-library/src/navigation-link/link-ui.js +++ b/packages/block-library/src/navigation-link/link-ui.js @@ -148,7 +148,6 @@ function LinkUIBlockInserter( { clientId, onBack, onSelectBlock } ) { export function LinkUI( props ) { const [ addingBlock, setAddingBlock ] = useState( false ); const [ focusAddBlockButton, setFocusAddBlockButton ] = useState( false ); - const [ showBackdrop, setShowBackdrop ] = useState( true ); const { saveEntityRecord } = useDispatch( coreStore ); const pagesPermissions = useResourcePermissions( 'pages' ); const postsPermissions = useResourcePermissions( 'posts' ); @@ -214,102 +213,88 @@ export function LinkUI( props ) { const { onClose: onSelectBlock } = props; return ( - <> - { showBackdrop && ( + + { ! addingBlock && ( + ) } + + { addingBlock && ( + { + setAddingBlock( false ); + setFocusAddBlockButton( true ); + } } + onSelectBlock={ onSelectBlock } + /> + ) } + ); } diff --git a/packages/block-library/src/navigation-link/style.scss b/packages/block-library/src/navigation-link/style.scss index 2676c8444ed59..f2f7d14f93fce 100644 --- a/packages/block-library/src/navigation-link/style.scss +++ b/packages/block-library/src/navigation-link/style.scss @@ -28,15 +28,3 @@ margin-left: $grid-unit-10; text-transform: uppercase; } - -// Ensure Popover `onClose` is fired for clicks on the canvas iframe. -// See https: //github.com/WordPress/gutenberg/pull/57756#discussion_r1475852009. -.components-popover-pointer-events-trap { - // Same z-index as popover, but rendered before the popover element - // in DOM order = it will display just under the popover - z-index: z-index(".components-popover"); - position: fixed; - inset: 0; - background-color: transparent; - cursor: pointer; -} diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index 11660b0621e4e..f435b5e5a8a95 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -293,7 +293,7 @@ export default function NavigationSubmenuEdit( { const blockProps = useBlockProps( { ref: useMergeRefs( [ setPopoverAnchor, listItemRef ] ), - className: classnames( 'wp-block-navigation-item', { + className: clsx( 'wp-block-navigation-item', { 'is-editing': isSelected || isParentOfSelectedBlock, 'is-dragging-within': isDraggingWithin, 'has-link': !! url, diff --git a/packages/block-library/src/navigation-submenu/index.php b/packages/block-library/src/navigation-submenu/index.php index b67f5ead8651e..92b55e291606e 100644 --- a/packages/block-library/src/navigation-submenu/index.php +++ b/packages/block-library/src/navigation-submenu/index.php @@ -87,6 +87,13 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) { $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] ); $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind ); + if ( is_post_type_archive() ) { + $queried_archive_link = get_post_type_archive_link( get_queried_object()->name ); + if ( $attributes['url'] === $queried_archive_link ) { + $is_active = true; + } + } + $show_submenu_indicators = isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon']; $open_on_click = isset( $block->context['openSubmenusOnClick'] ) && $block->context['openSubmenusOnClick']; $open_on_hover_and_click = isset( $block->context['openSubmenusOnClick'] ) && ! $block->context['openSubmenusOnClick'] && diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index eef6af390de78..c65e0c6224616 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -137,15 +137,6 @@ "type": "flex" } }, - "__experimentalStyle": { - "elements": { - "link": { - "color": { - "text": "inherit" - } - } - } - }, "interactivity": true, "renaming": false }, diff --git a/packages/block-library/src/navigation/edit/are-blocks-dirty.js b/packages/block-library/src/navigation/edit/are-blocks-dirty.js index 1d7fa8a7286f2..c4d1a8f76456a 100644 --- a/packages/block-library/src/navigation/edit/are-blocks-dirty.js +++ b/packages/block-library/src/navigation/edit/are-blocks-dirty.js @@ -30,7 +30,9 @@ const isDeepEqual = ( x, y, shouldSkip ) => { y !== null && y !== undefined ) { - if ( Object.keys( x ).length !== Object.keys( y ).length ) return false; + if ( Object.keys( x ).length !== Object.keys( y ).length ) { + return false; + } for ( const prop in x ) { if ( y.hasOwnProperty( prop ) ) { @@ -39,9 +41,12 @@ const isDeepEqual = ( x, y, shouldSkip ) => { return true; } - if ( ! isDeepEqual( x[ prop ], y[ prop ], shouldSkip ) ) + if ( ! isDeepEqual( x[ prop ], y[ prop ], shouldSkip ) ) { return false; - } else return false; + } + } else { + return false; + } } return true; diff --git a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js index 143bf8a3a2ce3..915e19694085d 100644 --- a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js +++ b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js @@ -11,7 +11,7 @@ function DeletedNavigationWarning( { onCreateNew } ) { { createInterpolateElement( __( - 'Navigation Menu has been deleted or is unavailable. ' + 'Navigation Menu has been deleted or is unavailable. ' ), { button: + { isSelected && showURLPopover && ( { - if ( variation.isActive ) return; + if ( variation.isActive ) { + return; + } variation.isActive = ( blockAttributes, variationAttributes ) => blockAttributes.service === variationAttributes.service; } ); diff --git a/packages/block-library/src/social-links/deprecated.js b/packages/block-library/src/social-links/deprecated.js index b5a56db3e102c..e13cdaba6a43c 100644 --- a/packages/block-library/src/social-links/deprecated.js +++ b/packages/block-library/src/social-links/deprecated.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classNames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -96,7 +96,7 @@ const deprecated = [ }, } = props; - const className = classNames( size, { + const className = clsx( size, { 'has-icon-color': iconColorValue, 'has-icon-background-color': iconBackgroundColorValue, [ `items-justified-${ itemsJustification }` ]: diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 52d26be50a6c9..bea4a4e6721ec 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classNames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -95,7 +95,7 @@ export function SocialLinksEdit( props ) { // Fallback color values are used maintain selections in case switching // themes and named colors in palette do not match. - const className = classNames( size, { + const className = clsx( size, { 'has-visible-labels': showLabels, 'has-icon-color': iconColor.color || iconColorValue, 'has-icon-background-color': diff --git a/packages/block-library/src/social-links/save.js b/packages/block-library/src/social-links/save.js index 07a9142416624..53d2243085048 100644 --- a/packages/block-library/src/social-links/save.js +++ b/packages/block-library/src/social-links/save.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classNames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -18,7 +18,7 @@ export default function save( props ) { }, } = props; - const className = classNames( size, { + const className = clsx( size, { 'has-visible-labels': showLabels, 'has-icon-color': iconColorValue, 'has-icon-background-color': iconBackgroundColorValue, diff --git a/packages/block-library/src/spacer/edit.js b/packages/block-library/src/spacer/edit.js index def3104c9b282..03fc59e07ed17 100644 --- a/packages/block-library/src/spacer/edit.js +++ b/packages/block-library/src/spacer/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -46,7 +46,7 @@ const ResizableSpacer = ( { return ( { - const cellClasses = classnames( { + const cellClasses = clsx( { [ `has-text-align-${ align }` ]: align, } ); @@ -395,13 +391,9 @@ const v3 = { const colorProps = getColorClassesAndStyles( attributes ); const borderProps = getBorderClassesAndStyles( attributes ); - const classes = classnames( - colorProps.className, - borderProps.className, - { - 'has-fixed-layout': hasFixedLayout, - } - ); + const classes = clsx( colorProps.className, borderProps.className, { + 'has-fixed-layout': hasFixedLayout, + } ); const hasCaption = ! RichText.isEmpty( caption ); @@ -421,7 +413,7 @@ const v3 = { { content, tag, scope, align }, cellIndex ) => { - const cellClasses = classnames( { + const cellClasses = clsx( { [ `has-text-align-${ align }` ]: align, } ); @@ -570,7 +562,7 @@ const v2 = { backgroundColor ); - const classes = classnames( backgroundClass, { + const classes = clsx( backgroundClass, { 'has-fixed-layout': hasFixedLayout, 'has-background': !! backgroundClass, } ); @@ -593,7 +585,7 @@ const v2 = { { content, tag, scope, align }, cellIndex ) => { - const cellClasses = classnames( { + const cellClasses = clsx( { [ `has-text-align-${ align }` ]: align, } ); @@ -746,7 +738,7 @@ const v1 = { backgroundColor ); - const classes = classnames( backgroundClass, { + const classes = clsx( backgroundClass, { 'has-fixed-layout': hasFixedLayout, 'has-background': !! backgroundClass, } ); diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index b8f239a01095d..f0c3ece790863 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -419,7 +419,7 @@ function TableEdit( { scope={ CellTag === 'th' ? scope : undefined } colSpan={ colspan } rowSpan={ rowspan } - className={ classnames( + className={ clsx( { [ `has-text-align-${ align }` ]: align, }, @@ -505,7 +505,7 @@ function TableEdit( { { ! isEmpty && ( { - const cellClasses = classnames( { + const cellClasses = clsx( { [ `has-text-align-${ align }` ]: align, } ); diff --git a/packages/block-library/src/template-part/variations.js b/packages/block-library/src/template-part/variations.js index 79881ee5f89e4..acd8af13508ba 100644 --- a/packages/block-library/src/template-part/variations.js +++ b/packages/block-library/src/template-part/variations.js @@ -31,10 +31,14 @@ export function enhanceTemplatePartVariations( settings, name ) { const { area, theme, slug } = blockAttributes; // We first check the `area` block attribute which is set during insertion. // This property is removed on the creation of a template part. - if ( area ) return area === variationAttributes.area; + if ( area ) { + return area === variationAttributes.area; + } // Find a matching variation from the created template part // by checking the entity's `area` property. - if ( ! slug ) return false; + if ( ! slug ) { + return false; + } const { getCurrentTheme, getEntityRecord } = select( coreDataStore ); const entity = getEntityRecord( diff --git a/packages/block-library/src/term-description/edit.js b/packages/block-library/src/term-description/edit.js index cd728ec051b60..c19d38abde513 100644 --- a/packages/block-library/src/term-description/edit.js +++ b/packages/block-library/src/term-description/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -20,7 +20,7 @@ export default function TermDescriptionEdit( { } ) { const { textAlign } = attributes; const blockProps = useBlockProps( { - className: classnames( { + className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), style: mergedStyle, diff --git a/packages/block-library/src/utils/caption.js b/packages/block-library/src/utils/caption.js index e365593dcc663..2c653a9cc7fb8 100644 --- a/packages/block-library/src/utils/caption.js +++ b/packages/block-library/src/utils/caption.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -94,7 +94,7 @@ export function Caption( { { * ``` */ export const registerBlockVariation = ( blockName, variation ) => { + if ( typeof variation.name !== 'string' ) { + console.warn( 'Variation names must be unique strings.' ); + } + dispatch( blocksStore ).addBlockVariations( blockName, variation ); }; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 5883f47536431..609e62fc7e84b 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -128,7 +128,9 @@ export function getSaveElement( ) { const blockType = normalizeBlockType( blockTypeOrName ); - if ( ! blockType?.save ) return null; + if ( ! blockType?.save ) { + return null; + } let { save } = blockType; diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index a97a456e0e22f..81f45f1999803 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -13,6 +13,7 @@ import { select, dispatch } from '@wordpress/data'; import { registerBlockType, registerBlockCollection, + registerBlockVariation, unregisterBlockCollection, unregisterBlockType, setFreeformContentHandlerName, @@ -26,6 +27,7 @@ import { getBlockType, getBlockTypes, getBlockSupport, + getBlockVariations, hasBlockSupport, isReusableBlock, unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase @@ -398,6 +400,51 @@ describe( 'blocks', () => { } ); } ); + it( 'should merge settings provided by server and client', () => { + const blockName = 'core/test-block-with-merged-settings'; + unstable__bootstrapServerSideBlockDefinitions( { + [ blockName ]: { + variations: [ + { name: 'foo', label: 'Foo' }, + { name: 'baz', label: 'Baz', description: 'Testing' }, + ], + }, + } ); + + const blockType = { + title: 'block settings merge', + variations: [ + { name: 'bar', label: 'Bar' }, + { name: 'baz', label: 'Baz', icon: 'layout' }, + ], + }; + registerBlockType( blockName, blockType ); + expect( getBlockType( blockName ) ).toEqual( { + name: blockName, + save: expect.any( Function ), + title: 'block settings merge', + icon: { src: BLOCK_ICON_DEFAULT }, + attributes: {}, + providesContext: {}, + usesContext: [], + keywords: [], + selectors: {}, + supports: {}, + styles: [], + variations: [ + { name: 'foo', label: 'Foo' }, + { + description: 'Testing', + name: 'baz', + label: 'Baz', + icon: 'layout', + }, + { name: 'bar', label: 'Bar' }, + ], + blockHooks: {}, + } ); + } ); + // This test can be removed once the polyfill for blockHooks gets removed. it( 'should polyfill blockHooks using metadata on the client when not set on the server', () => { const blockName = 'tests/hooked-block'; @@ -1365,6 +1412,26 @@ describe( 'blocks', () => { expect( isReusableBlock( block ) ).toBe( false ); } ); } ); + + describe( 'registerBlockVariation', () => { + it( 'should warn when registering block variation without a name', () => { + registerBlockType( 'core/variation-block', defaultBlockSettings ); + registerBlockVariation( 'core/variation-block', { + title: 'Variation Title', + description: 'Variation description', + } ); + + expect( console ).toHaveWarnedWith( + 'Variation names must be unique strings.' + ); + expect( getBlockVariations( 'core/variation-block' ) ).toEqual( [ + { + title: 'Variation Title', + description: 'Variation description', + }, + ] ); + } ); + } ); } ); /* eslint-enable react/forbid-elements */ diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index a1c85910210b7..a68937586f927 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -334,9 +334,13 @@ export function __experimentalSanitizeBlockAttributes( name, attributes ) { */ export function __experimentalGetBlockAttributesNamesByRole( name, role ) { const attributes = getBlockType( name )?.attributes; - if ( ! attributes ) return []; + if ( ! attributes ) { + return []; + } const attributesNames = Object.keys( attributes ); - if ( ! role ) return attributesNames; + if ( ! role ) { + return attributesNames; + } return attributesNames.filter( ( attributeName ) => attributes[ attributeName ]?.__experimentalRole === role diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index 889f59b55e392..154f74c6ab729 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -33,6 +33,37 @@ const LEGACY_CATEGORY_MAPPING = { layout: 'design', }; +/** + * Merge block variations bootstrapped from the server and client. + * + * When a variation is registered in both places, its properties are merged. + * + * @param {Array} bootstrappedVariations - A block type variations from the server. + * @param {Array} clientVariations - A block type variations from the client. + * @return {Array} The merged array of block variations. + */ +function mergeBlockVariations( + bootstrappedVariations = [], + clientVariations = [] +) { + const result = [ ...bootstrappedVariations ]; + + clientVariations.forEach( ( clientVariation ) => { + const index = result.findIndex( + ( bootstrappedVariation ) => + bootstrappedVariation.name === clientVariation.name + ); + + if ( index !== -1 ) { + result[ index ] = { ...result[ index ], ...clientVariation }; + } else { + result.push( clientVariation ); + } + } ); + + return result; +} + /** * Takes the unprocessed block type settings, merges them with block type metadata * and applies all the existing filters for the registered block type. @@ -46,6 +77,8 @@ const LEGACY_CATEGORY_MAPPING = { export const processBlockType = ( name, blockSettings ) => ( { select } ) => { + const bootstrappedBlockType = select.getBootstrappedBlockType( name ); + const blockType = { name, icon: BLOCK_ICON_DEFAULT, @@ -56,11 +89,14 @@ export const processBlockType = selectors: {}, supports: {}, styles: [], - variations: [], blockHooks: {}, save: () => null, - ...select.getBootstrappedBlockType( name ), + ...bootstrappedBlockType, ...blockSettings, + variations: mergeBlockVariations( + bootstrappedBlockType?.variations, + blockSettings?.variations + ), }; const settings = applyFilters( diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index 6aa4230ab920a..d9651ea02bcbe 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 5.40.0 (2024-05-02) + +## 5.39.0 (2024-04-19) + ## 5.38.0 (2024-04-03) ## 5.37.0 (2024-03-21) diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 979d09608d5c7..012941eb05270 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "5.38.0", + "version": "5.40.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index 97fb8999300b8..4346fc5eabaf0 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Internal + +- Replaced `classnames` package with the faster and smaller `clsx` package ([#61138](https://github.com/WordPress/gutenberg/pull/61138)). + +## 0.28.0 (2024-05-02) + +## 0.27.0 (2024-04-19) + ## 0.26.0 (2024-04-03) ## 0.25.0 (2024-03-21) diff --git a/packages/commands/package.json b/packages/commands/package.json index c3f7d3ead0f46..a1d98f9f77226 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "0.26.0", + "version": "0.28.0", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,7 +34,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/private-apis": "file:../private-apis", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "cmdk": "^0.2.0" }, "peerDependencies": { diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 7d5f6f2574777..3a129823fedb3 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -2,7 +2,7 @@ * External dependencies */ import { Command, useCommandState } from 'cmdk'; -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -55,7 +55,7 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) { > @@ -126,7 +126,7 @@ export function CommandMenuGroup( { isContextual, search, setLoader, close } ) { > @@ -221,7 +221,9 @@ export function CommandMenu() { /** @type {import('react').KeyboardEventHandler} */ ( event ) => { // Bails to avoid obscuring the effect of the preceding handler(s). - if ( event.defaultPrevented ) return; + if ( event.defaultPrevented ) { + return; + } event.preventDefault(); if ( isOpen ) { diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 34d1a328671a8..d8263245eebf2 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,22 +2,57 @@ ## Unreleased +### Internal + +- Replaced `classnames` package with the faster and smaller `clsx` package ([#61138](https://github.com/WordPress/gutenberg/pull/61138)). + +## 27.5.0 (2024-05-02) + +### Enhancements + +- Replaced `classnames` package with the faster and smaller `clsx` package ([#61138](https://github.com/WordPress/gutenberg/pull/61138)). +- `InputControl`: Add a password visibility toggle story ([#60898](https://github.com/WordPress/gutenberg/pull/60898)). +- `View`: Fix prop types ([#60919](https://github.com/WordPress/gutenberg/pull/60919)). +- `Placeholder`: Unify appearance across. ([#59275](https://github.com/WordPress/gutenberg/pull/59275)). +- `Toolbar`: Adjust top toolbar to use same metrics as block toolbar ([#61126](https://github.com/WordPress/gutenberg/pull/61126)). + +### Bug Fix + +- `SlotFill`: fixed missing `getServerSnapshot` parameter in slot map ([#60943](https://github.com/WordPress/gutenberg/pull/60943)). +- `Panel`: Fix issue with collapsing panel header ([#61319](https://github.com/WordPress/gutenberg/pull/61319)). + +### Enhancements + +- `DropZone`: Avoid a media query on mount [#60546](https://github.com/WordPress/gutenberg/pull/60546)). +- `ComboboxControl`: Simplify string normalization ([#60893](https://github.com/WordPress/gutenberg/pull/60893)). + +### Internal + +- `FontSizerPicker`: Improve docs for default units ([#60996](https://github.com/WordPress/gutenberg/pull/60996)). + +## 27.4.0 (2024-04-19) + ### Deprecation - `Navigation`: Soft deprecate component ([#59182](https://github.com/WordPress/gutenberg/pull/59182)). ### Enhancements +- `Tooltip`: Make tests faster ([#60897](https://github.com/WordPress/gutenberg/pull/60897)). - `ExternalLink`: Use unicode arrow instead of svg icon ([#60255](https://github.com/WordPress/gutenberg/pull/60255)). - `ProgressBar`: Move the indicator width styles from emotion to a CSS variable ([#60388](https://github.com/WordPress/gutenberg/pull/60388)). -- `Text`: Add `text-wrap: pretty;` to improve wrapping. ([#60164](https://github.com/WordPress/gutenberg/pull/60164)). -- `Navigator`: Navigation to the active path doesn't create a new location history. ([#60561](https://github.com/WordPress/gutenberg/pull/60561)) -- `FormToggle`: Forwards ref to input. ([#60234](https://github.com/WordPress/gutenberg/pull/60234)). -- `ToggleControl`: Forwards ref to FormToggle. ([#60234](https://github.com/WordPress/gutenberg/pull/60234)). +- `Text`: Add `text-wrap: pretty;` to improve wrapping ([#60164](https://github.com/WordPress/gutenberg/pull/60164)). +- `Navigator`: Navigation to the active path doesn't create a new location history ([#60561](https://github.com/WordPress/gutenberg/pull/60561)). +- `FormToggle`: Forwards ref to input ([#60234](https://github.com/WordPress/gutenberg/pull/60234)). +- `ToggleControl`: Forwards ref to FormToggle ([#60234](https://github.com/WordPress/gutenberg/pull/60234)). +- `CheckboxControl`: Update help text alignment ([#60787](https://github.com/WordPress/gutenberg/pull/60787)). ### Bug Fix +- `Truncate`: Fix link control link preview when it displays long URLs ([#60890](https://github.com/WordPress/gutenberg/pull/60890)). + - `ProgressBar`: Fix CSS variable with invalid value ([#60576](https://github.com/WordPress/gutenberg/pull/60576)). +- `CheckboxControl`: Fix label text wrap ([#60787](https://github.com/WordPress/gutenberg/pull/60787)). ### Experimental @@ -29,6 +64,8 @@ - `CheckboxControl`: Streamline size styles ([#60475](https://github.com/WordPress/gutenberg/pull/60475)). - Deprecate `reduceMotion` util ([#60839](https://github.com/WordPress/gutenberg/pull/60839)). - `InputBase`: Simplify management of focus styles. Affects all components based on `InputControl` (e.g. `SearchControl`, `NumberControl`, `UnitControl`), as well as `SelectControl`, `CustomSelectControl`, and `TreeSelect` ([#60226](https://github.com/WordPress/gutenberg/pull/60226)). +- Removed dependency on `valtio`, replaced its usage in `SlotFill` with a custom object [#60879](https://github.com/WordPress/gutenberg/pull/60879)). +- `CustomSelectControlV2`: Support disabled in item types ([#60896](https://github.com/WordPress/gutenberg/pull/60896)). ## 27.3.0 (2024-04-03) @@ -63,6 +100,7 @@ - `TextControl`: Add typings for `date`, `time` and `datetime-local` ([#59666](https://github.com/WordPress/gutenberg/pull/59666)). - `Text`, `Heading`, `ItemGroup` : Update the line height from 1.2 to 1.4 ([#60041](https://github.com/WordPress/gutenberg/pull/60041)). +- `Autocomplete` : match the autocomplete styling to that of List View and Command Palette([#60131](https://github.com/WordPress/gutenberg/pull/60131)). ### Deprecation diff --git a/packages/components/package.json b/packages/components/package.json index 188e214e79bd4..308765f08a694 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "27.3.0", + "version": "27.5.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -60,7 +60,7 @@ "@wordpress/rich-text": "file:../rich-text", "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", - "classnames": "^2.3.1", + "clsx": "^2.1.1", "colord": "^2.7.0", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", @@ -76,8 +76,7 @@ "react-colorful": "^5.3.1", "remove-accents": "^0.5.0", "use-lilius": "^2.0.5", - "uuid": "^9.0.1", - "valtio": "1.7.0" + "uuid": "^9.0.1" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/components/src/alignment-matrix-control/icon.tsx b/packages/components/src/alignment-matrix-control/icon.tsx index 33528e63fc87f..bacc9771cf3e4 100644 --- a/packages/components/src/alignment-matrix-control/icon.tsx +++ b/packages/components/src/alignment-matrix-control/icon.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * Internal dependencies @@ -28,7 +28,7 @@ function AlignmentMatrixControlIcon( { const alignIndex = getAlignmentIndex( value ); const scale = ( size / BASE_SIZE ).toFixed( 2 ); - const classes = classnames( + const classes = clsx( 'component-alignment-matrix-control-icon', className ); diff --git a/packages/components/src/alignment-matrix-control/index.tsx b/packages/components/src/alignment-matrix-control/index.tsx index 3de0e401187d5..eaec8a285b0c5 100644 --- a/packages/components/src/alignment-matrix-control/index.tsx +++ b/packages/components/src/alignment-matrix-control/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -61,17 +61,16 @@ export function AlignmentMatrixControl( { activeId: getItemId( baseId, value ), setActiveId: ( nextActiveId ) => { const nextValue = getItemValue( baseId, nextActiveId ); - if ( nextValue ) onChange?.( nextValue ); + if ( nextValue ) { + onChange?.( nextValue ); + } }, rtl: isRTL(), } ); const activeId = compositeStore.useState( 'activeId' ); - const classes = classnames( - 'component-alignment-matrix-control', - className - ); + const classes = clsx( 'component-alignment-matrix-control', className ); return ( -1 ? index : undefined; diff --git a/packages/components/src/angle-picker-control/index.tsx b/packages/components/src/angle-picker-control/index.tsx index dcd0fe6f94a2e..b824660fddb13 100644 --- a/packages/components/src/angle-picker-control/index.tsx +++ b/packages/components/src/angle-picker-control/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import type { ForwardedRef } from 'react'; -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -46,7 +46,7 @@ function UnforwardedAnglePickerControl( onChange( inputValue ); }; - const classes = classnames( 'components-angle-picker-control', className ); + const classes = clsx( 'components-angle-picker-control', className ); const unitText = °; const [ prefixedUnitText, suffixedUnitText ] = isRTL() diff --git a/packages/components/src/animate/index.tsx b/packages/components/src/animate/index.tsx index f73090bf85dba..e7aaf72d24380 100644 --- a/packages/components/src/animate/index.tsx +++ b/packages/components/src/animate/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * Internal dependencies @@ -23,24 +23,21 @@ function getDefaultOrigin( type?: GetAnimateOptions[ 'type' ] ) { */ export function getAnimateClassName( options: GetAnimateOptions ) { if ( options.type === 'loading' ) { - return classnames( 'components-animate__loading' ); + return clsx( 'components-animate__loading' ); } const { type, origin = getDefaultOrigin( type ) } = options; if ( type === 'appear' ) { const [ yAxis, xAxis = 'center' ] = origin.split( ' ' ); - return classnames( 'components-animate__appear', { + return clsx( 'components-animate__appear', { [ 'is-from-' + xAxis ]: xAxis !== 'center', [ 'is-from-' + yAxis ]: yAxis !== 'middle', } ); } if ( type === 'slide-in' ) { - return classnames( - 'components-animate__slide-in', - 'is-from-' + origin - ); + return clsx( 'components-animate__slide-in', 'is-from-' + origin ); } return undefined; diff --git a/packages/components/src/autocomplete/autocompleter-ui.tsx b/packages/components/src/autocomplete/autocompleter-ui.tsx index 663316c39b32e..a3e3cb503c483 100644 --- a/packages/components/src/autocomplete/autocompleter-ui.tsx +++ b/packages/components/src/autocomplete/autocompleter-ui.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -55,7 +55,9 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) { popoverRef, useRefEffect( ( node ) => { - if ( ! contentRef.current ) return; + if ( ! contentRef.current ) { + return; + } // If the popover is rendered in a different document than // the content, we need to duplicate the options list in the @@ -139,7 +141,7 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) { role="option" aria-selected={ index === selectedIndex } disabled={ option.isDisabled } - className={ classnames( + className={ clsx( 'components-autocomplete__result', className, { diff --git a/packages/components/src/autocomplete/index.tsx b/packages/components/src/autocomplete/index.tsx index 944eebf83de06..3bde3a73999f6 100644 --- a/packages/components/src/autocomplete/index.tsx +++ b/packages/components/src/autocomplete/index.tsx @@ -253,7 +253,9 @@ export function useAutocomplete( { useEffect( () => { if ( ! textContent ) { - if ( autocompleter ) reset(); + if ( autocompleter ) { + reset(); + } return; } @@ -277,7 +279,9 @@ export function useAutocomplete( { ); if ( ! completer ) { - if ( autocompleter ) reset(); + if ( autocompleter ) { + reset(); + } return; } @@ -293,7 +297,9 @@ export function useAutocomplete( { // significantly. This could happen, for example, if `matchingWhileBackspacing` // is true and one of the "words" end up being too long. If that's the case, // it will be caught by this guard. - if ( tooDistantFromTrigger ) return; + if ( tooDistantFromTrigger ) { + return; + } const mismatch = filteredOptions.length === 0; const wordsFromTrigger = textWithoutTrigger.split( /\s/ ); @@ -318,7 +324,9 @@ export function useAutocomplete( { backspacing.current && wordsFromTrigger.length <= 3; if ( mismatch && ! ( matchingWhileBackspacing || hasOneTriggerWord ) ) { - if ( autocompleter ) reset(); + if ( autocompleter ) { + reset(); + } return; } @@ -333,7 +341,9 @@ export function useAutocomplete( { textAfterSelection ) ) { - if ( autocompleter ) reset(); + if ( autocompleter ) { + reset(); + } return; } @@ -341,12 +351,16 @@ export function useAutocomplete( { /^\s/.test( textWithoutTrigger ) || /\s\s+$/.test( textWithoutTrigger ) ) { - if ( autocompleter ) reset(); + if ( autocompleter ) { + reset(); + } return; } if ( ! /[\u0000-\uFFFF]*$/.test( textWithoutTrigger ) ) { - if ( autocompleter ) reset(); + if ( autocompleter ) { + reset(); + } return; } diff --git a/packages/components/src/autocomplete/style.scss b/packages/components/src/autocomplete/style.scss index 4d04b3b8b52cf..fdb29fe577f20 100644 --- a/packages/components/src/autocomplete/style.scss +++ b/packages/components/src/autocomplete/style.scss @@ -1,6 +1,6 @@ .components-autocomplete__popover .components-popover__content { - padding: $grid-unit-20; - min-width: 220px; + padding: $grid-unit-10; + min-width: 200px; } .components-autocomplete__result.components-button { @@ -10,7 +10,13 @@ text-align: left; width: 100%; - &.is-selected { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent; + &:focus:not(:disabled) { + @include block-toolbar-button-style__focus(); + } + + &.is-selected, + &:not(:disabled,[aria-disabled="true"]):active { + background: $components-color-accent; + color: $white; } } diff --git a/packages/components/src/base-control/index.tsx b/packages/components/src/base-control/index.tsx index 344643f28cfd7..2de6ced0a5465 100644 --- a/packages/components/src/base-control/index.tsx +++ b/packages/components/src/base-control/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * Internal dependencies @@ -124,10 +124,7 @@ export const VisualLabel = ( { return ( { children } diff --git a/packages/components/src/box-control/test/index.tsx b/packages/components/src/box-control/test/index.tsx index 1ea3c84aae922..681e7721d0c13 100644 --- a/packages/components/src/box-control/test/index.tsx +++ b/packages/components/src/box-control/test/index.tsx @@ -80,6 +80,28 @@ describe( 'BoxControl', () => { expect( input ).toHaveValue( '50' ); expect( screen.getByRole( 'slider' ) ).toHaveValue( '50' ); } ); + + it( 'should render the number input with a default min value of 0', () => { + render( {} } /> ); + + const input = screen.getByRole( 'textbox', { name: 'All sides' } ); + + expect( input ).toHaveAttribute( 'min', '0' ); + } ); + + it( 'should pass down `inputProps` to the underlying number input', () => { + render( + {} } + inputProps={ { min: 10, max: 50 } } + /> + ); + + const input = screen.getByRole( 'textbox', { name: 'All sides' } ); + + expect( input ).toHaveAttribute( 'min', '10' ); + expect( input ).toHaveAttribute( 'max', '50' ); + } ); } ); describe( 'Reset', () => { diff --git a/packages/components/src/button-group/index.tsx b/packages/components/src/button-group/index.tsx index 2eb7451e3e00d..fb2659c2a0d7d 100644 --- a/packages/components/src/button-group/index.tsx +++ b/packages/components/src/button-group/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; import type { ForwardedRef } from 'react'; /** @@ -20,7 +20,7 @@ function UnforwardedButtonGroup( ref: ForwardedRef< HTMLDivElement > ) { const { className, ...restProps } = props; - const classes = classnames( 'components-button-group', className ); + const classes = clsx( 'components-button-group', className ); return (
    diff --git a/packages/components/src/button/index.tsx b/packages/components/src/button/index.tsx index 456e563c8159e..b60f6af039e59 100644 --- a/packages/components/src/button/index.tsx +++ b/packages/components/src/button/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; import type { ComponentPropsWithoutRef, ForwardedRef, @@ -141,7 +141,7 @@ export function UnforwardedButton( 'mixed', ]; - const classes = classnames( 'components-button', className, { + const classes = clsx( 'components-button', className, { 'is-next-40px-default-size': __next40pxDefaultSize, 'is-secondary': variant === 'secondary', 'is-primary': variant === 'primary', diff --git a/packages/components/src/card/test/__snapshots__/index.tsx.snap b/packages/components/src/card/test/__snapshots__/index.tsx.snap index 0b723732e3dba..cc1a754e1d37c 100644 --- a/packages/components/src/card/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/card/test/__snapshots__/index.tsx.snap @@ -8,8 +8,8 @@ Snapshot Diff: @@ -1,8 +1,8 @@
    @@ -25,8 +25,8 @@ Snapshot Diff: @@ -1,8 +1,8 @@
    @@ -42,8 +42,8 @@ Snapshot Diff: @@ -1,8 +1,8 @@