diff --git a/.eslintrc.js b/.eslintrc.js index 0b0c71c39a2664..177f3cf35b8ccf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -83,6 +83,72 @@ const restrictedImports = [ }, ]; +const restrictedSyntax = [ + // NOTE: We can't include the forward slash in our regex or + // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern) + // here. That's why we use \\u002F in the regexes below. + { + selector: + 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', + message: 'Path access on WordPress dependencies is not allowed.', + }, + { + selector: + 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + + majorMinorRegExp + + '/]', + message: + 'Deprecated functions must be removed before releasing this version.', + }, + { + selector: + 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', + message: + 'This method is deprecated. You should use the more explicit API methods available.', + }, + { + selector: + 'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]', + message: 'Prefer page.waitForSelector instead.', + }, + { + selector: 'JSXAttribute[name.name="id"][value.type="Literal"]', + message: + 'Do not use string literals for IDs; use withInstanceId instead.', + }, + { + // Discourage the usage of `Math.random()` as it's a code smell + // for UUID generation, for which we already have a higher-order + // component: `withInstanceId`. + selector: + 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', + message: + 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', + }, + { + selector: + 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', + message: + 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', + }, + { + selector: + 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]', + message: + 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', + }, +]; + +/** `no-restricted-syntax` rules for components. */ +const restrictedSyntaxComponents = [ + { + selector: + 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]', + message: + '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)', + }, +]; + module.exports = { root: true, extends: [ @@ -92,6 +158,7 @@ module.exports = { ], globals: { wp: 'off', + globalThis: 'readonly', }, settings: { jsdoc: { @@ -103,8 +170,12 @@ module.exports = { rules: { 'jest/expect-expect': 'off', 'react/jsx-boolean-value': 'error', + 'react/jsx-curly-brace-presence': [ + 'error', + { props: 'never', children: 'never' }, + ], '@wordpress/dependency-group': 'error', - '@wordpress/is-gutenberg-plugin': 'error', + '@wordpress/wp-global-usage': 'error', '@wordpress/react-no-unsafe-timeout': 'error', '@wordpress/i18n-text-domain': [ 'error', @@ -142,63 +213,7 @@ module.exports = { disallowTypeAnnotations: false, }, ], - 'no-restricted-syntax': [ - 'error', - // NOTE: We can't include the forward slash in our regex or - // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern) - // here. That's why we use \\u002F in the regexes below. - { - selector: - 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', - message: - 'Path access on WordPress dependencies is not allowed.', - }, - { - selector: - 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + - majorMinorRegExp + - '/]', - message: - 'Deprecated functions must be removed before releasing this version.', - }, - { - selector: - 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', - message: - 'This method is deprecated. You should use the more explicit API methods available.', - }, - { - selector: - 'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]', - message: 'Prefer page.waitForSelector instead.', - }, - { - selector: 'JSXAttribute[name.name="id"][value.type="Literal"]', - message: - 'Do not use string literals for IDs; use withInstanceId instead.', - }, - { - // Discourage the usage of `Math.random()` as it's a code smell - // for UUID generation, for which we already have a higher-order - // component: `withInstanceId`. - selector: - 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', - message: - 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', - }, - { - selector: - 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', - message: - 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', - }, - { - selector: - 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]', - message: - 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', - }, - ], + 'no-restricted-syntax': [ 'error', ...restrictedSyntax ], }, overrides: [ { @@ -248,6 +263,20 @@ module.exports = { ], }, }, + { + files: [ + 'packages/*/src/**/*.[tj]s?(x)', + 'storybook/stories/**/*.[tj]s?(x)', + ], + excludedFiles: [ '**/*.native.js' ], + rules: { + 'no-restricted-syntax': [ + 'error', + ...restrictedSyntax, + ...restrictedSyntaxComponents, + ], + }, + }, { files: [ // Components package. @@ -367,6 +396,7 @@ module.exports = { rules: { 'no-restricted-syntax': [ 'error', + ...restrictedSyntax, { selector: ':matches(Literal[value=/--wp-admin-theme-/],TemplateElement[value.cooked=/--wp-admin-theme-/])', diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index f6527c3de9d978..477cd4fbacb400 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -18,3 +18,6 @@ c56e8a1910ed74f405b74bbb12fe81dea974e5c3 # Autofix eslint curly rule. 0221522f253e094b277a1485b7a2d186cb172632 + +# ESLint: Enable react/jsx-curly-brace-presence +5d4baa9ab5f57d207cc3a048003216a8574574d9 diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 149faee274206e..a572074f72c9c8 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: token: ${{ secrets.GUTENBERG_TOKEN }} show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -165,7 +165,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: ${{ needs.bump-version.outputs.release_branch || github.ref }} show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -222,7 +222,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 2 ref: ${{ needs.bump-version.outputs.release_branch }} @@ -311,14 +311,14 @@ jobs: if: ${{ endsWith( needs.bump-version.outputs.new_version, '-rc.1' ) }} steps: - name: Checkout (for CLI) - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: path: main ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Checkout (for publishing) - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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 1065421044373b..2e7f2c98305fb2 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 1 show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/check-backport-changelog.yml b/.github/workflows/check-backport-changelog.yml new file mode 100644 index 00000000000000..99d7e1ca5b53a8 --- /dev/null +++ b/.github/workflows/check-backport-changelog.yml @@ -0,0 +1,59 @@ +name: Verify Core Backport Changlog + +on: + pull_request: + types: [opened, synchronize, labeled, unlabeled] + paths: + - 'lib/**' + - '!lib/load.php' + - '!lib/experiments-page.php' + - '!lib/experimental/**' + - 'phpunit/**' + - '!phpunit/experimental/**' + - '!phpunit/blocks/**' + - 'packages/**/*.php' + - '!packages/block-library/**' + - '!packages/e2e-tests/**' +jobs: + check: + name: Check CHANGELOG diff + runs-on: ubuntu-latest + steps: + - name: 'Get PR commit count' + run: echo "PR_COMMIT_COUNT=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}" + - name: Checkout code + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: ${{ env.PR_COMMIT_COUNT }} + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + - name: 'Fetch relevant history from origin' + run: git fetch origin ${{ github.event.pull_request.base.ref }} + - name: Check CHANGELOG status + if: ${{ !contains(github.event.pull_request.labels.*.name, 'No Core Sync Required') && !contains(github.event.pull_request.labels.*.name, 'Backport from WordPress Core') }} + env: + PR_NUMBER: ${{ github.event.number }} + run: | + changelog_folder="backport-changelog" + + # Find any changelog file that contains the Gutenberg PR link + gutenberg_pr_url="https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}" + changelog_file=$(grep -rl "[-*] ${gutenberg_pr_url}" "${changelog_folder}" | head -n 1) + + # Confirm that there is an entry containing the Gutenberg PR link + if [[ -z "${changelog_file}" ]]; then + echo "Please create a core backport PR and add a file with the path /.md in the $changelog_folder folder with the core backport PR URL and a list item with this PR URL." + echo "If changes are related to an existing, open core PR, you may add this PR URL to the core PR's file." + echo "See $changelog_folder/readme.md for more information." + exit 1 + fi + + core_pr_number=$(basename "${changelog_file}" .md) + core_pr_url="https://github\.com/WordPress/wordpress-develop/pull/${core_pr_number}" + + # Confirm that the entry has the correct core backport PR URL. + if ! grep -q -e "${core_pr_url}" "${changelog_file}"; then + echo "Please update the content of ${changelog_file} to include the core backport PR URL, or update the file name to match the core backport PR number." + exit 1 + fi diff --git a/.github/workflows/check-components-changelog.yml b/.github/workflows/check-components-changelog.yml index d995d641fae57d..ccc6efac3c3788 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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 7c26cb6e14e760..2a7ea040f8b493 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -20,11 +20,11 @@ jobs: strategy: fail-fast: false matrix: - node: ['20', '21'] + node: ['20', '22'] os: ['macos-latest', 'ubuntu-latest', 'windows-latest'] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 16680038e0db64..c4f165d42699d2 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -27,7 +27,7 @@ jobs: totalParts: [8] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -102,7 +102,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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 633f62d5ed28c9..b444b8f974df77 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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 7f239652774df1..9090ab305dadfa 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -25,6 +25,7 @@ concurrency: jobs: performance: + timeout-minutes: 60 name: Run performance tests runs-on: ubuntu-latest if: ${{ github.repository == 'WordPress/gutenberg' }} @@ -32,7 +33,7 @@ jobs: WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/php-changes-detection.yml b/.github/workflows/php-changes-detection.yml index 6d0a0a451a5153..d003d149a9a090 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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@a29e8b565651ce417abb5db7164b4a2ad8b6155c # v44.4.0 + uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2 with: files: | lib/** @@ -54,7 +54,7 @@ jobs: This pull request has changed or added PHP files. Please confirm whether these changes need to be synced to WordPress Core, and therefore featured in the next release of WordPress. - If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core Github repository](https://github.com/WordPress/wordpress-develop) soon after this pull request is merged. + If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core GitHub repository](https://github.com/WordPress/wordpress-develop) soon after this pull request is merged. If you're unsure, you can always ask for help in the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). @@ -76,7 +76,7 @@ jobs: This pull request has changed or added PHP files. Please confirm whether these changes need to be synced to WordPress Core, and therefore featured in the next release of WordPress. - If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core Github repository](https://github.com/WordPress/wordpress-develop) soon after this pull request is merged. + If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core GitHub repository](https://github.com/WordPress/wordpress-develop) soon after this pull request is merged. If you're unsure, you can always ask for help in the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 94397afd7b4bca..ab139c19e6ddec 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: path: cli ref: trunk @@ -39,7 +39,7 @@ jobs: - name: Checkout (for publishing) if: ${{ github.event.inputs.release_type != 'wp' }} - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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 099203bbffe720..61c522474a7b3d 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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 a4dce407d1c0ff..bd23fb219ce752 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -60,7 +60,7 @@ jobs: - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@6b0df4b0efb23bb0ec63d881db79aefbc976e4b2 # v2.30.1 + uses: reactivecircus/android-emulator-runner@77986be26589807b8ebab3fde7bbf5c60dabec32 # v2.31.0 with: api-level: ${{ matrix.api-level }} force-avd-creation: false @@ -71,7 +71,7 @@ jobs: script: echo "Generated AVD snapshot for caching." - name: Run tests - uses: reactivecircus/android-emulator-runner@6b0df4b0efb23bb0ec63d881db79aefbc976e4b2 # v2.30.1 + uses: reactivecircus/android-emulator-runner@77986be26589807b8ebab3fde7bbf5c60dabec32 # v2.31.0 with: api-level: ${{ matrix.api-level }} force-avd-creation: false diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 516f783c11e401..83228bd87b85ca 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0 + - uses: ruby/setup-ruby@d5fb7a202fc07872cb44f00ba8e6197b70cb0c55 # v1.179.0 with: # `.ruby-version` file location working-directory: packages/react-native-editor/ios diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index ff3fe96d505f6f..32d7c609681196 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 7486ea32533e6a..dfe301386849f6 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index a4a639e183d5bf..a813e4d2d8f5ba 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -27,12 +27,12 @@ jobs: strategy: fail-fast: false matrix: - node: ['20', '21'] + node: ['20', '22'] shard: ['1/4', '2/4', '3/4', '4/4'] steps: - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -66,11 +66,11 @@ jobs: strategy: fail-fast: false matrix: - node: ['20', '21'] + node: ['20', '22'] steps: - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -170,7 +170,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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@c665c7a15b5295c2488ac8a87af9cb806cd72198 # v2.30.4 + uses: shivammathur/setup-php@fc14643b0a99ee9db10a3c025a33d76544fa3761 # v2.30.5 with: php-version: '${{ matrix.php }}' ini-file: development @@ -281,12 +281,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@c665c7a15b5295c2488ac8a87af9cb806cd72198 # v2.30.4 + uses: shivammathur/setup-php@fc14643b0a99ee9db10a3c025a33d76544fa3761 # v2.30.5 with: php-version: '7.4' coverage: none @@ -351,7 +351,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 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 8f57a749b0601d..9bb40a3f06cbc0 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -51,7 +51,7 @@ jobs: return $? } - # Only update trunk *if* the published release's version in Github is GREATER + # Only update trunk *if* the published release's version in GitHub is GREATER # than the version currently published in the WP plugins repo. If not, then it # will upload it as a new tag. shouldUpdateTrunk=false @@ -96,7 +96,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: ${{ matrix.branch }} token: ${{ secrets.GUTENBERG_TOKEN }} diff --git a/backport-changelog/6.6/6279.md b/backport-changelog/6.6/6279.md new file mode 100644 index 00000000000000..f372c33d339b8a --- /dev/null +++ b/backport-changelog/6.6/6279.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6279 + +* https://github.com/WordPress/gutenberg/pull/60063 diff --git a/backport-changelog/6.6/6522.md b/backport-changelog/6.6/6522.md new file mode 100644 index 00000000000000..172464cf664de9 --- /dev/null +++ b/backport-changelog/6.6/6522.md @@ -0,0 +1,5 @@ +https://github.com/WordPress/wordpress-develop/pull/6522 + +* https://github.com/WordPress/gutenberg/pull/60106 +* https://github.com/WordPress/gutenberg/pull/60228 +* https://github.com/WordPress/gutenberg/pull/61638 diff --git a/backport-changelog/6.6/6559.md b/backport-changelog/6.6/6559.md new file mode 100644 index 00000000000000..176a2e04f02574 --- /dev/null +++ b/backport-changelog/6.6/6559.md @@ -0,0 +1,6 @@ +https://github.com/WordPress/wordpress-develop/pull/6559 + +* https://github.com/WordPress/gutenberg/pull/60349 +* https://github.com/WordPress/gutenberg/pull/60464 +* https://github.com/WordPress/gutenberg/pull/60491 +* https://github.com/WordPress/gutenberg/pull/61757 diff --git a/backport-changelog/6.6/6567.md b/backport-changelog/6.6/6567.md new file mode 100644 index 00000000000000..8f200bed3b5c09 --- /dev/null +++ b/backport-changelog/6.6/6567.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/6567 + +* https://github.com/WordPress/gutenberg/pull/47271 +* https://github.com/WordPress/gutenberg/pull/61774 diff --git a/backport-changelog/6.6/6590.md b/backport-changelog/6.6/6590.md new file mode 100644 index 00000000000000..47ef89e0db40cb --- /dev/null +++ b/backport-changelog/6.6/6590.md @@ -0,0 +1,5 @@ +https://github.com/WordPress/wordpress-develop/pull/6590 + +* https://github.com/WordPress/gutenberg/pull/59531 +* https://github.com/WordPress/gutenberg/pull/61182 +* https://github.com/WordPress/gutenberg/pull/61717 diff --git a/backport-changelog/6.6/6605.md b/backport-changelog/6.6/6605.md new file mode 100644 index 00000000000000..c45a37d77e660b --- /dev/null +++ b/backport-changelog/6.6/6605.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6605 + +* https://github.com/WordPress/gutenberg/pull/61851 diff --git a/backport-changelog/6.6/6616.md b/backport-changelog/6.6/6616.md new file mode 100644 index 00000000000000..bb35d6c74493cf --- /dev/null +++ b/backport-changelog/6.6/6616.md @@ -0,0 +1,7 @@ +https://github.com/WordPress/wordpress-develop/pull/6616 + +* https://github.com/WordPress/gutenberg/pull/58409 +* https://github.com/WordPress/gutenberg/pull/61328 +* https://github.com/WordPress/gutenberg/pull/61842 +* https://github.com/WordPress/gutenberg/pull/62199 +* https://github.com/WordPress/gutenberg/pull/62252 diff --git a/backport-changelog/6.6/6656.md b/backport-changelog/6.6/6656.md new file mode 100644 index 00000000000000..f0bf8900335f49 --- /dev/null +++ b/backport-changelog/6.6/6656.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6656 + +* https://github.com/WordPress/gutenberg/pull/60715 \ No newline at end of file diff --git a/backport-changelog/6.6/6662.md b/backport-changelog/6.6/6662.md new file mode 100644 index 00000000000000..5b25fc99304919 --- /dev/null +++ b/backport-changelog/6.6/6662.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/6662 + +* https://github.com/WordPress/gutenberg/pull/57908 +* https://github.com/WordPress/gutenberg/pull/62125 diff --git a/backport-changelog/6.6/6694.md b/backport-changelog/6.6/6694.md new file mode 100644 index 00000000000000..a9eb5a7f37ef5b --- /dev/null +++ b/backport-changelog/6.6/6694.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6694 + +* https://github.com/WordPress/gutenberg/pull/60694 diff --git a/backport-changelog/6.6/6731.md b/backport-changelog/6.6/6731.md new file mode 100644 index 00000000000000..b8671873251659 --- /dev/null +++ b/backport-changelog/6.6/6731.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6731 + +* https://github.com/WordPress/gutenberg/pull/62299 diff --git a/backport-changelog/6.6/6737.md b/backport-changelog/6.6/6737.md new file mode 100644 index 00000000000000..84e2234ca4e79a --- /dev/null +++ b/backport-changelog/6.6/6737.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6737 + +* https://github.com/WordPress/gutenberg/pull/62305 diff --git a/backport-changelog/6.6/6744.md b/backport-changelog/6.6/6744.md new file mode 100644 index 00000000000000..032f5420cb7184 --- /dev/null +++ b/backport-changelog/6.6/6744.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6744 + +* https://github.com/WordPress/gutenberg/pull/62355 \ No newline at end of file diff --git a/backport-changelog/6.6/6756.md b/backport-changelog/6.6/6756.md new file mode 100644 index 00000000000000..688b258c095faa --- /dev/null +++ b/backport-changelog/6.6/6756.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/6756 + +* https://github.com/WordPress/gutenberg/pull/62461 +* https://github.com/WordPress/gutenberg/pull/62495 diff --git a/backport-changelog/6.6/6785.md b/backport-changelog/6.6/6785.md new file mode 100644 index 00000000000000..ad26227bf12565 --- /dev/null +++ b/backport-changelog/6.6/6785.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6785 + +* https://github.com/WordPress/gutenberg/pull/62459 diff --git a/backport-changelog/6.6/6797.md b/backport-changelog/6.6/6797.md new file mode 100644 index 00000000000000..630b677655ddce --- /dev/null +++ b/backport-changelog/6.6/6797.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6797 + +* https://github.com/WordPress/gutenberg/pull/62526 \ No newline at end of file diff --git a/backport-changelog/6.6/6824.md b/backport-changelog/6.6/6824.md new file mode 100644 index 00000000000000..7dc6e090f01498 --- /dev/null +++ b/backport-changelog/6.6/6824.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6824 + +* https://github.com/WordPress/gutenberg/pull/62550 diff --git a/backport-changelog/6.6/6825.md b/backport-changelog/6.6/6825.md new file mode 100644 index 00000000000000..73e5f46600d892 --- /dev/null +++ b/backport-changelog/6.6/6825.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6825 + +* https://github.com/WordPress/gutenberg/pull/62552 diff --git a/backport-changelog/6.7/6750.md b/backport-changelog/6.7/6750.md new file mode 100644 index 00000000000000..257ebe3a5aa698 --- /dev/null +++ b/backport-changelog/6.7/6750.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6750 + +* https://github.com/WordPress/gutenberg/pull/62357 \ No newline at end of file diff --git a/backport-changelog/readme.md b/backport-changelog/readme.md new file mode 100644 index 00000000000000..200cb9db404865 --- /dev/null +++ b/backport-changelog/readme.md @@ -0,0 +1,16 @@ +# Core Backport Changelog + +Any PR that makes changes to be backported to [core](https://github.com/WordPress/wordpress-develop) should log a core PR here. It's possible to have multiple Gutenberg PRs link to a single core backport PR. The core backport PR can remain open as long as wanted/needed. The entries are sorted by core release (in folders), and each entry should be an md file with the core PR number as the file name, and the link to the Gutenberg PR in the file content. The file content should start with the core PR URL, followed by a Markdown list of Gutenberg PRs (see example). Files are used to avoid rebase conflicts. + +If you think a file path is wrongly flagged as needing a core backport PR, you can add it to the list of exceptions in `.github/workflows/check-backport-changelog.yml`. + +## Example + +Path: `{wp-release-number-x.x}/{core-pr-number}.md`, e.g. `6.6/1234.md`. +File content: +```md +https://github.com/WordPress/wordpress-develop/pull/{core-pr-number} + +* https://github.com/WordPress/gutenberg/pull/{first-gb-pr-number} +* https://github.com/WordPress/gutenberg/pull/{second-gb-pr-number} +``` diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index d81bc017fc0be6..e6b2b6f0692a56 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -466,7 +466,7 @@ function getCurrentBranch() { */ async function reportGhUnavailable() { console.log( - 'Github CLI is not setup. This script will not be able to automatically' + 'GitHub CLI is not setup. This script will not be able to automatically' ); console.log( 'comment on the processed PRs and remove the backport label from them.' diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js index 3f1512ef0feb78..06e30efc6c6dc9 100644 --- a/bin/packages/build-worker.js +++ b/bin/packages/build-worker.js @@ -13,6 +13,8 @@ const postcss = require( 'postcss' ); */ const getBabelConfig = require( './get-babel-config' ); +const isDev = process.env.NODE_ENV === 'development'; + /** * Path to packages directory. * @@ -27,10 +29,12 @@ const PACKAGES_DIR = path * * @type {Object} */ -const JS_ENVIRONMENTS = { - main: 'build', - module: 'build-module', -}; +const JS_ENVIRONMENTS = isDev + ? { module: 'build-module' } + : { + main: 'build', + module: 'build-module', + }; /** * Promisified fs.readFile. @@ -122,9 +126,10 @@ async function buildCSS( file ) { data: ''.concat( '@use "sass:math";', importLists, contents ), } ); - const result = await postcss( - require( '@wordpress/postcss-plugins-preset' ) - ).process( builtSass.css, { + const result = await postcss( [ + require( 'postcss-local-keyframes' ), + ...require( '@wordpress/postcss-plugins-preset' ), + ] ).process( builtSass.css, { from: 'src/app.css', to: 'dest/app.css', } ); diff --git a/bin/packages/check-build-type-declaration-files.js b/bin/packages/check-build-type-declaration-files.js index 74c7cfa5e3c2ac..ffc68c83b8da8e 100644 --- a/bin/packages/check-build-type-declaration-files.js +++ b/bin/packages/check-build-type-declaration-files.js @@ -70,7 +70,7 @@ async function getDecFile( packagePath ) { async function typecheckDeclarations( file ) { return new Promise( ( resolve, reject ) => { exec( - `npx tsc --target esnext --moduleResolution node --noEmit ${ file }`, + `npx tsc --target esnext --moduleResolution node --noEmit --skipLibCheck "${ file }"`, ( error, stdout, stderr ) => { if ( error ) { reject( { file, error, stderr, stdout } ); diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index 652c6e97a6c0a2..43164a80ab24a4 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -606,11 +606,11 @@ function getEntry( issue ) { /** * Builds a formatted string of the Issue/PR title with a link - * to the Github URL for that item. + * to the GitHub URL for that item. * * @param {string} title the title of the Issue/PR. * @param {number} number the ID/number of the Issue/PR. - * @param {string} url the URL of the Github Issue/PR. + * @param {string} url the URL of the GitHub Issue/PR. * @return {string} the formatted item */ function getFormattedItemDescription( title, number, url ) { @@ -856,7 +856,7 @@ function sortFeatureGroups( featureGroups ) { } /** - * Returns a list of PRs created by first time contributors based on the Github + * Returns a list of PRs created by first time contributors based on the GitHub * label associated with the PR. Also filters out any "bots". * * @param {IssuesListForRepoResponseItem[]} pullRequests List of pull requests. diff --git a/bin/plugin/commands/test/changelog.js b/bin/plugin/commands/test/changelog.js index 12420fd1e4b6f9..9c9d423d18d1cb 100644 --- a/bin/plugin/commands/test/changelog.js +++ b/bin/plugin/commands/test/changelog.js @@ -25,7 +25,7 @@ import _pullRequests from './fixtures/pull-requests.json'; import botPullRequestFixture from './fixtures/bot-pull-requests.json'; /** - * pull-requests.json is a static snapshot of real data from the Github API. + * pull-requests.json is a static snapshot of real data from the GitHub API. * We merge this with dummy fixture data for a "bot" pull request so as to * ensure future updates to the pull-requests.json doesn't reduce test coverage * of filtering out of bot PRs. diff --git a/changelog.txt b/changelog.txt index 7a3ba488afd9f9..8679f4a40ba9ec 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,95 +1,566 @@ == Changelog == -= 18.4.0-rc.1 = - += 18.5.0 = ## Changelog ### Features -#### Site Editor -- Adds "Template Parts" command to site editor. ([61287](https://github.com/WordPress/gutenberg/pull/61287)) +#### Global Styles +- Add defaultSpacingSizes option (theme.json v3). ([61842](https://github.com/WordPress/gutenberg/pull/61842)) +- Edit/create shadows in global styles. ([60706](https://github.com/WordPress/gutenberg/pull/60706)) +- Relocate Background Image controls to sit under Layout. ([61886](https://github.com/WordPress/gutenberg/pull/61886)) + +#### Block Library +- Enable shadow support for cover block. ([61883](https://github.com/WordPress/gutenberg/pull/61883)) + +#### Block bindings +- Allow editing in post meta source. ([61753](https://github.com/WordPress/gutenberg/pull/61753)) + +#### Script Modules API +- Add script module data implementation. ([61658](https://github.com/WordPress/gutenberg/pull/61658)) + +#### Synced Patterns +- Add `__default` binding for pattern overrides. ([60694](https://github.com/WordPress/gutenberg/pull/60694)) + +#### Block Styles +- Extend block style variations as mechanism for achieving section styling. ([57908](https://github.com/WordPress/gutenberg/pull/57908)) ### Enhancements +- Block card: Fix typographic widow. ([61438](https://github.com/WordPress/gutenberg/pull/61438)) +- Block settings: Update variant of "Apply globally" Button component to secondary. ([61850](https://github.com/WordPress/gutenberg/pull/61850)) +- Editor: Align the Post Format control design with the rest of the post sidebar controls. ([62066](https://github.com/WordPress/gutenberg/pull/62066)) +- Editor: Polish the style of some of the post summary rows. ([61645](https://github.com/WordPress/gutenberg/pull/61645)) +- Format Library: Refactor 'Inline Image' edit component. ([62135](https://github.com/WordPress/gutenberg/pull/62135)) +- Playwright end-to-end Utils: Add fullscreenMode option to createNewPost. ([61766](https://github.com/WordPress/gutenberg/pull/61766)) +- Post Sticky Toggle: Improve the design. ([62012](https://github.com/WordPress/gutenberg/pull/62012)) +- Post Summary: Move PostTemplatePanel below URL and Author. ([62137](https://github.com/WordPress/gutenberg/pull/62137)) +- Remove trash button in post/page inspector. ([61792](https://github.com/WordPress/gutenberg/pull/61792)) +- Shadows instead of borders on interface skeleton. ([61835](https://github.com/WordPress/gutenberg/pull/61835)) +- Tweak contextual block toolbar position. ([61836](https://github.com/WordPress/gutenberg/pull/61836)) +- Update: For synced entities the icon should be purple. ([62024](https://github.com/WordPress/gutenberg/pull/62024)) +- Update: Implement new author panel design. ([61362](https://github.com/WordPress/gutenberg/pull/61362)) +- Update: Implement new parent and order design. ([61918](https://github.com/WordPress/gutenberg/pull/61918)) +- Update: Move duplicate pattern and template part actions to the editor package. ([61879](https://github.com/WordPress/gutenberg/pull/61879)) + +#### Site Editor +- Block Editor: Check for multiple block usage in the block-editor package. ([62086](https://github.com/WordPress/gutenberg/pull/62086)) +- Copy custom CSS between variations when switching. ([61752](https://github.com/WordPress/gutenberg/pull/61752)) +- Data views: Align page headers. ([62115](https://github.com/WordPress/gutenberg/pull/62115)) +- Inspector summary rows: Make tooltips appear middle-left. ([61815](https://github.com/WordPress/gutenberg/pull/61815)) +- Inspector: Add '/' prefix to Link button. ([62073](https://github.com/WordPress/gutenberg/pull/62073)) +- Inspector: Display home / posts page badge. ([62071](https://github.com/WordPress/gutenberg/pull/62071)) +- Inspector: Remove revisions panel. ([61867](https://github.com/WordPress/gutenberg/pull/61867)) +- Make post meta row button treatment consistent. ([61954](https://github.com/WordPress/gutenberg/pull/61954)) +- Remove 'Manage...' prefix in Pages / Templates data views. ([62107](https://github.com/WordPress/gutenberg/pull/62107)) +- Remove the details pages. ([61741](https://github.com/WordPress/gutenberg/pull/61741)) +- Update actions order in site editor for template and template parts. ([61803](https://github.com/WordPress/gutenberg/pull/61803)) +- Use site title as a link. ([61258](https://github.com/WordPress/gutenberg/pull/61258)) +- [Site Editor]: Add create pattern button in patterns page. ([60302](https://github.com/WordPress/gutenberg/pull/60302)) +- withRegistryProvider: Prevent intermediate state with no children. ([61859](https://github.com/WordPress/gutenberg/pull/61859)) + +#### Data Views +- Add badge to title for posts & front pages. ([61718](https://github.com/WordPress/gutenberg/pull/61718)) +- Clarify `date` value in Pages. ([61709](https://github.com/WordPress/gutenberg/pull/61709)) +- DataViews: `label` prop in Actions API can be either a `string` or a `function`. ([61942](https://github.com/WordPress/gutenberg/pull/61942)) +- Fix pagination position on pages with short lists. ([61712](https://github.com/WordPress/gutenberg/pull/61712)) +- Pages data view: Add Pending and Private views. ([62138](https://github.com/WordPress/gutenberg/pull/62138)) +- Pages sidebar: Adds published & scheduled items. ([62021](https://github.com/WordPress/gutenberg/pull/62021)) +- Stop Patterns data view header shrinking. ([61801](https://github.com/WordPress/gutenberg/pull/61801)) +- Update grid layout on small screens. ([61820](https://github.com/WordPress/gutenberg/pull/61820)) +- Update list layout action styling. ([61797](https://github.com/WordPress/gutenberg/pull/61797)) +- Update page component (and some data view elements) spacing metrics. ([61333](https://github.com/WordPress/gutenberg/pull/61333)) +- Visually hide 'Actions' column header. ([61710](https://github.com/WordPress/gutenberg/pull/61710)) + #### Global Styles -- Add aspect ratio presets support via theme.json. ([47271](https://github.com/WordPress/gutenberg/pull/47271)) -- Background images: Remove required "file" prop. ([61387](https://github.com/WordPress/gutenberg/pull/61387)) -- Change "Solid" tab to "Color". ([61366](https://github.com/WordPress/gutenberg/pull/61366)) -- Improve panel title and description for palette. ([61365](https://github.com/WordPress/gutenberg/pull/61365)) -- Tweak palette panel spacing and empty message. ([61368](https://github.com/WordPress/gutenberg/pull/61368)) -- Update color variations. ([61334](https://github.com/WordPress/gutenberg/pull/61334)) +- Add block-level Text Alignment UI. ([61717](https://github.com/WordPress/gutenberg/pull/61717)) +- Add option to remove site-wide theme background image. ([61998](https://github.com/WordPress/gutenberg/pull/61998)) +- Background image: Add support for relative theme path URLs in top-level theme.json styles. ([61271](https://github.com/WordPress/gutenberg/pull/61271)) +- Background image: Update controls defaults and layout. ([62000](https://github.com/WordPress/gutenberg/pull/62000)) +- Background images: Add defaults for background size. ([62046](https://github.com/WordPress/gutenberg/pull/62046)) +- Don't filter out typography variations where the heading and body fonts are the same. ([61327](https://github.com/WordPress/gutenberg/pull/61327)) +- Make color variations fit in a bit better visually. ([61617](https://github.com/WordPress/gutenberg/pull/61617)) +- Make it clearer how to edit a site's palette. ([61364](https://github.com/WordPress/gutenberg/pull/61364)) +- Move type presets below elements. ([61863](https://github.com/WordPress/gutenberg/pull/61863)) +- Restore the default variation to the color and typography style tiles. ([61901](https://github.com/WordPress/gutenberg/pull/61901)) +- Show shadow tool by default under global styles. ([61981](https://github.com/WordPress/gutenberg/pull/61981)) + +#### Components +- Add vw and vh units to the custom font size picker. ([60607](https://github.com/WordPress/gutenberg/pull/60607)) +- CustomSelectControlV2: Use `InputBase` for styling. ([60261](https://github.com/WordPress/gutenberg/pull/60261)) +- Tabs: Indicator animation. ([60560](https://github.com/WordPress/gutenberg/pull/60560)) +- Try: Add CSS Custom Properties to CSS types. ([61872](https://github.com/WordPress/gutenberg/pull/61872)) #### Zoom Out -- Editor: Enable Zoom-out mode in the post editor. ([61293](https://github.com/WordPress/gutenberg/pull/61293)) -- Keep original viewport width (single scale). ([61424](https://github.com/WordPress/gutenberg/pull/61424)) -- Open inserter sidebar when clicking on inserter buttons on zoom-out mode. ([61434](https://github.com/WordPress/gutenberg/pull/61434)) -- Remove experimental zoom out control. ([61509](https://github.com/WordPress/gutenberg/pull/61509)) -- Zoomed Out View: Don't close the inserter. ([61004](https://github.com/WordPress/gutenberg/pull/61004)) +- Hide inserters behind the experiment flag. ([61866](https://github.com/WordPress/gutenberg/pull/61866)) +- Inserter: Auto-close the inserter unless the zoom out experiment is on. ([61856](https://github.com/WordPress/gutenberg/pull/61856)) +- Show the inserters only when a section is selected. ([61559](https://github.com/WordPress/gutenberg/pull/61559)) +- The patterns tab behind a new experiment. ([61601](https://github.com/WordPress/gutenberg/pull/61601)) -#### Data Views -- Dataviews: Add: Bulk actions toolbar. ([59714](https://github.com/WordPress/gutenberg/pull/59714)) -- Align list and table layout visuals. ([61157](https://github.com/WordPress/gutenberg/pull/61157)) -- DataViews: Add actions to list layout. ([60805](https://github.com/WordPress/gutenberg/pull/60805)) -- Make pattern preview click area larger. ([61250](https://github.com/WordPress/gutenberg/pull/61250)) -- Post Type Actions: Unify the list of available actions. ([61520](https://github.com/WordPress/gutenberg/pull/61520)) +#### Block Editor +- Adjust pattern list items resting, hover, focus styles. ([61831](https://github.com/WordPress/gutenberg/pull/61831)) +- Tweak pattern categories sidebar. ([62113](https://github.com/WordPress/gutenberg/pull/62113)) +- Writing flow: Remove first empty paragraph on Backspace. ([61889](https://github.com/WordPress/gutenberg/pull/61889)) -#### Post Editor -- Editor: Add global styles to settings using existing context code. ([61556](https://github.com/WordPress/gutenberg/pull/61556)) -- Post Actions: Display a notice after moving a post into the trash. ([61670](https://github.com/WordPress/gutenberg/pull/61670)) -- Post Publish Flow: Simplify status term. ([61386](https://github.com/WordPress/gutenberg/pull/61386)) +#### Block bindings +- Add Block Bindings Panel to Block Inspector. ([61527](https://github.com/WordPress/gutenberg/pull/61527)) +- Add indicator for metadata changes to Save Panel when reviewing modified entities. ([61811](https://github.com/WordPress/gutenberg/pull/61811)) +- Lock binding editing with functions. ([61734](https://github.com/WordPress/gutenberg/pull/61734)) + +#### Block Variations +- Detect active variation correctly based on RichText attribute. ([62325](https://github.com/WordPress/gutenberg/pull/62325)) +- Have `getActiveBlockVariation` return variation with highest specificity. ([62031](https://github.com/WordPress/gutenberg/pull/62031)) +- Support dot notation in `isActive` string array. ([62088](https://github.com/WordPress/gutenberg/pull/62088)) #### Layout -- Check child layout exists before generating classname. ([61392](https://github.com/WordPress/gutenberg/pull/61392)) -- Hide Image block resizer when inside a grid layout. ([61603](https://github.com/WordPress/gutenberg/pull/61603)) -- Stabilise grid layout visualiser and resizer. ([61640](https://github.com/WordPress/gutenberg/pull/61640)) +- More consistent root padding. ([60715](https://github.com/WordPress/gutenberg/pull/60715)) +- Try using coloured overlay instead of border for grid visualiser. ([61390](https://github.com/WordPress/gutenberg/pull/61390)) #### Block Library -- Add block class name to the list block. ([56469](https://github.com/WordPress/gutenberg/pull/56469)) -- Embeds: Add Bluesky variation. ([61352](https://github.com/WordPress/gutenberg/pull/61352)) -- Site Logo: Add setting labels via the 'register_setting' method. ([61351](https://github.com/WordPress/gutenberg/pull/61351)) +- Added Bluesky icon to the Social Icon Block. ([61372](https://github.com/WordPress/gutenberg/pull/61372)) +- Media & Text: Replace the deprecated __experimentalImageSizeControl with ResolutionTool. ([57540](https://github.com/WordPress/gutenberg/pull/57540)) + +#### Inspector Controls +- Align both "Design" pattern list panels. ([62161](https://github.com/WordPress/gutenberg/pull/62161)) + +#### Post Editor +- Add home template details to inspector controls. ([61762](https://github.com/WordPress/gutenberg/pull/61762)) + +#### Interactivity API +- Clarify some warning messages. ([61720](https://github.com/WordPress/gutenberg/pull/61720)) + +#### Patterns +- Adjust the icons and text of the binding connected blocks. ([61560](https://github.com/WordPress/gutenberg/pull/61560)) + + +### Bug Fixes + +- Editor: Only render the site logo once if there's a fill. ([62320](https://github.com/WordPress/gutenberg/pull/62320)) +- Interactivity API: Increase directive `wp-each-child` priority. ([62293](https://github.com/WordPress/gutenberg/pull/62293))w +- Compose: Fix 'useFocusOnMount' cleanup callback. ([62053](https://github.com/WordPress/gutenberg/pull/62053)) +- Do not auto save post status changes. ([62171](https://github.com/WordPress/gutenberg/pull/62171)) +- Editor: Fix canvas padding in post editor. ([61893](https://github.com/WordPress/gutenberg/pull/61893)) +- EntityProvider: Avoid remounts and simplify. ([61882](https://github.com/WordPress/gutenberg/pull/61882)) +- Fix shadow and border for pattern categories panel. ([62158](https://github.com/WordPress/gutenberg/pull/62158)) +- Image Block: Conditionally Render Block Control Based on Component Presence. ([62132](https://github.com/WordPress/gutenberg/pull/62132)) +- Interactivity API: Fix null and number strings as namespaces runtime error. ([61960](https://github.com/WordPress/gutenberg/pull/61960)) +- PostCardPanel: Fix ESLint error. ([62109](https://github.com/WordPress/gutenberg/pull/62109)) +- Remove build-types/ clean from clean:Packages. ([62008](https://github.com/WordPress/gutenberg/pull/62008)) +- Script Modules: Fix private method reflection access. ([62154](https://github.com/WordPress/gutenberg/pull/62154)) +- ServerSideRender: Fix data loading in development mode. ([62140](https://github.com/WordPress/gutenberg/pull/62140)) +- Shadow Panel: Make subtitle translatable. ([62022](https://github.com/WordPress/gutenberg/pull/62022)) +- Site Editor: Fix the Root Padding styles. ([61906](https://github.com/WordPress/gutenberg/pull/61906)) +- Writing flow: Fix heading crash on split (via paste). ([61900](https://github.com/WordPress/gutenberg/pull/61900)) +- e2e: Fix Site Editor Styles test. ([62111](https://github.com/WordPress/gutenberg/pull/62111)) + +#### Post Editor +- Consolidate and fix `delete` and `edit` post actions. ([61912](https://github.com/WordPress/gutenberg/pull/61912)) +- Consolidate and fix `rename` post action. ([61857](https://github.com/WordPress/gutenberg/pull/61857)) +- Document Bar: Decode HTML entities and take into account cases where there is no title. ([62087](https://github.com/WordPress/gutenberg/pull/62087)) +- Editor: Don't apply purple accent to the unsynced pattern title. ([61704](https://github.com/WordPress/gutenberg/pull/61704)) +- Editor: Ensure Copy button in sidebar copies whole permalink, *with* URL protocol. ([61876](https://github.com/WordPress/gutenberg/pull/61876)) +- Editor: Fix the 'DocumentBar' position for long titles. ([61691](https://github.com/WordPress/gutenberg/pull/61691)) +- Editor: Render publish date control when the status is `future`(scheduled). ([62070](https://github.com/WordPress/gutenberg/pull/62070)) +- Editor: Unify button size in pre-publish panel. ([62123](https://github.com/WordPress/gutenberg/pull/62123)) +- Editor: Use edited entity for post actions. ([61892](https://github.com/WordPress/gutenberg/pull/61892)) +- Fix read only post status styles. ([61722](https://github.com/WordPress/gutenberg/pull/61722)) +- Post Actions: Hide the trash action for auto-drafts. ([61865](https://github.com/WordPress/gutenberg/pull/61865)) + +#### Block Editor +- Inserter: Update Openverse API URLs. ([62241](https://github.com/WordPress/gutenberg/pull/62241)) +- Fix being unable to switch modes while inserter is open. ([61563](https://github.com/WordPress/gutenberg/pull/61563)) +- Fix editor inserter tabs indicator. ([61973](https://github.com/WordPress/gutenberg/pull/61973)) +- Fix positioning of close icons in panels to be consistent. ([61832](https://github.com/WordPress/gutenberg/pull/61832)) +- Fix syncing of publish date between publish and post status panel. ([62165](https://github.com/WordPress/gutenberg/pull/62165)) +- Improve link conrol preview when show button text label is enabled. ([61726](https://github.com/WordPress/gutenberg/pull/61726)) +- Inserter: Show all blocks (alternative). ([62169](https://github.com/WordPress/gutenberg/pull/62169)) +- InspectorControls: Text not displayed when "Show button text labels" is enabled. ([61949](https://github.com/WordPress/gutenberg/pull/61949)) +- Link Control: Fix focus handlers in development mode. ([62141](https://github.com/WordPress/gutenberg/pull/62141)) +- Media & Text block: Remove the link option when the featured image is used. ([60510](https://github.com/WordPress/gutenberg/pull/60510)) +- Writing flow: Fix paste for input fields. ([61389](https://github.com/WordPress/gutenberg/pull/61389)) + +#### Block Library +- Classic block: Fix content syncing effect for React StrictMode. ([62051](https://github.com/WordPress/gutenberg/pull/62051)) +- Don't steal focus when opening browse all blocks. ([61975](https://github.com/WordPress/gutenberg/pull/61975)) +- Fix: The latest post block - post titles overlapping. ([61356](https://github.com/WordPress/gutenberg/pull/61356)) +- Fixed : Update `alt text decision tree` links to be translatable. ([62076](https://github.com/WordPress/gutenberg/pull/62076)) +- Fixed: Custom HTML Block should display content in LTR layout for all languages. ([62083](https://github.com/WordPress/gutenberg/pull/62083)) +- More block: Fix React warning when adding custom text. ([61936](https://github.com/WordPress/gutenberg/pull/61936)) +- useUploadMediaFromBlobURL: Prevent duplicate uploads in StrictMode. ([62059](https://github.com/WordPress/gutenberg/pull/62059)) + +#### Global Styles +- Fix make dimensions.aspectRatios key of theme.json files translatable. ([61774](https://github.com/WordPress/gutenberg/pull/61774)) +- Hide the presets panel for when there are less or exactly one presets available. ([62074](https://github.com/WordPress/gutenberg/pull/62074)) +- Prevent Typography panel title from wrapping. ([62124](https://github.com/WordPress/gutenberg/pull/62124)) +- Shadow Panel: Generates unique shadow slugs by finding max suffix and incrementing it. ([61997](https://github.com/WordPress/gutenberg/pull/61997)) +- Styles: try wrapping with :Root to fix reset styles. ([61638](https://github.com/WordPress/gutenberg/pull/61638)) +- Transform Styles: Update selector so that styles work when custom fields panel is active. ([62121](https://github.com/WordPress/gutenberg/pull/62121)) + +#### Site Editor +- Align the template title to the center in the 'Add template' screen. ([62175](https://github.com/WordPress/gutenberg/pull/62175)) +- Close publish sidebar if not in `edit` mode. ([61707](https://github.com/WordPress/gutenberg/pull/61707)) +- Fix the site editor Admin Bar menu item. ([61851](https://github.com/WordPress/gutenberg/pull/61851)) +- Use a consistent snackbar position. ([61756](https://github.com/WordPress/gutenberg/pull/61756)) #### Components -- Do not render FormTokenField label when not defined. ([61336](https://github.com/WordPress/gutenberg/pull/61336)) -- Placeholder: Tweak placeholder style. ([61590](https://github.com/WordPress/gutenberg/pull/61590)) +- Fix: The focus styles for tabPanel. ([61317](https://github.com/WordPress/gutenberg/pull/61317)) +- InputControl: Fix z-index issue causing slider dots to appear in front of the Appearance dropdown. ([61937](https://github.com/WordPress/gutenberg/pull/61937)) +- getAutocompleterUI: Don't redefine ListBox component on every render. ([61877](https://github.com/WordPress/gutenberg/pull/61877)) + +#### Synced Patterns +- Block Bindings: Filter pattern overrides source in bindings panel. ([62015](https://github.com/WordPress/gutenberg/pull/62015)) +- Fix detaching patterns when a pattern has overrides, but there are no override values. ([62014](https://github.com/WordPress/gutenberg/pull/62014)) + +#### Block bindings +- Don't show non-existing and not supported attributes in block bindings panel. ([62183](https://github.com/WordPress/gutenberg/pull/62183)) + +#### Layout +- Remove extra bracket in the site editor root padding styles. ([62159](https://github.com/WordPress/gutenberg/pull/62159)) + +#### Block Styles +- Fix block style variation styles for blocks with complex selectors. ([62125](https://github.com/WordPress/gutenberg/pull/62125)) + +#### Code Editor +- Editor: Unify text/code editor between post and site editors. ([61934](https://github.com/WordPress/gutenberg/pull/61934)) + +#### Page Content Focus +- Remove lock icons from Content blocks inner blocks when editing a page in the site editor. ([61922](https://github.com/WordPress/gutenberg/pull/61922)) #### Patterns -- Only add the selected pattern category in metadata during insertion. ([61557](https://github.com/WordPress/gutenberg/pull/61557)) +- Templates: Only resolve patterns for REST API endpoints. ([61757](https://github.com/WordPress/gutenberg/pull/61757)) -#### Block Editor -- Block editor: Add a keyboard shortcut to create group from the selected blocks. ([46972](https://github.com/WordPress/gutenberg/pull/46972)) -- Enhance block outlines and selection interactions. ([60757](https://github.com/WordPress/gutenberg/pull/60757)) -- Tiny tweak to position close button properly in the inserter. ([61461](https://github.com/WordPress/gutenberg/pull/61461)) +#### Interactivity API +- Turn named capturing groups back into numbered ones inside `toVdom`. ([61728](https://github.com/WordPress/gutenberg/pull/61728)) + +#### Block API +- Fix: Enable Text Align UI to be controlled correctly with theme.json. ([61182](https://github.com/WordPress/gutenberg/pull/61182)) + +#### REST API +- Return an empty object when no fallback templates are found (wp/v2/templates/lookup). ([60925](https://github.com/WordPress/gutenberg/pull/60925)) + + +### Accessibility + +#### Global Styles +- Shadow Panel: Improve a11y and fix browser console error. ([61980](https://github.com/WordPress/gutenberg/pull/61980)) + +#### Data Views +- Always show Actions table header. ([61847](https://github.com/WordPress/gutenberg/pull/61847)) + +#### Block Library +- Fix: Adds help props for description of Play Inline toggle. ([61310](https://github.com/WordPress/gutenberg/pull/61310)) -#### Extensibility -- PluginSidebar: Show pin/unpin button on the site eitor. ([61448](https://github.com/WordPress/gutenberg/pull/61448)) + +### Performance + +- Perf: Batch block list settings in single action. ([61329](https://github.com/WordPress/gutenberg/pull/61329)) +- Remove additional call to `WP_Theme_JSON_Gutenberg::__construct`. ([61262](https://github.com/WordPress/gutenberg/pull/61262)) + +#### Interactivity API +- Introduce `wp-on-async` directive as performant alternative over synchronous `wp-on` directive. ([61885](https://github.com/WordPress/gutenberg/pull/61885)) + +#### Post Editor +- DocumentBar: Only selected data needed for rendering. ([61706](https://github.com/WordPress/gutenberg/pull/61706)) + + +### Experiments + +#### Interactivity API +- Use output buffer and HTML tag processor to inject directives on BODY tag for full-page client-side navigation. ([61212](https://github.com/WordPress/gutenberg/pull/61212)) + + +### Documentation + +- Add JSDoc to PostVisibility, PostVisibilityCheck, and PostVisibilityLabel. ([61735](https://github.com/WordPress/gutenberg/pull/61735)) +- Add PostURL component documentation. ([61737](https://github.com/WordPress/gutenberg/pull/61737)) +- Add a section about block filters to the Filters and Hooks doc. ([61771](https://github.com/WordPress/gutenberg/pull/61771)) +- Add an example and improve readability of the Block Filters doc. ([61770](https://github.com/WordPress/gutenberg/pull/61770)) +- Add docblock to PostTitle and PostTitleRaw component. ([61740](https://github.com/WordPress/gutenberg/pull/61740)) +- Add documentation for DocumentBar. ([61733](https://github.com/WordPress/gutenberg/pull/61733)) +- Add documentation for PostFeaturedImage, PostFeaturedImageCheck, PostFeaturedImagePanel. ([61165](https://github.com/WordPress/gutenberg/pull/61165)) +- Add documentation for PostLastRevision, PostLastRevisionCheck, PostLastRevisionPanel components. ([61166](https://github.com/WordPress/gutenberg/pull/61166)) +- Add documentation for PostSchedule, PostScheduleCheck, PostSchedulePanel, PostScheduleLabel, usePostScheduleLabel components. ([61345](https://github.com/WordPress/gutenberg/pull/61345)) +- Add documentation for the EditorNotices component. ([61736](https://github.com/WordPress/gutenberg/pull/61736)) +- Add documentation for the EditorProvider and ExperimentalEditorProvider components. ([61739](https://github.com/WordPress/gutenberg/pull/61739)) +- Added missing @global documentation. ([61537](https://github.com/WordPress/gutenberg/pull/61537)) +- Changelog: Add note about removing legacy operators. ([62013](https://github.com/WordPress/gutenberg/pull/62013)) +- Docs: Fix spacing in PHP doc block in comments block. ([61911](https://github.com/WordPress/gutenberg/pull/61911)) +- EditorBoundary editor component. ([61950](https://github.com/WordPress/gutenberg/pull/61950)) +- Fix typo. ([61830](https://github.com/WordPress/gutenberg/pull/61830)) +- Fix: Block library README.md link. ([62081](https://github.com/WordPress/gutenberg/pull/62081)) +- Fix: Custom block editor link. ([61962](https://github.com/WordPress/gutenberg/pull/61962)) +- For `PostTextEditor` component. ([62099](https://github.com/WordPress/gutenberg/pull/62099)) +- LocalAutosaveMonitor editor component. ([61951](https://github.com/WordPress/gutenberg/pull/61951)) +- PageTemplate + PostTemplatePanel editor components. ([61961](https://github.com/WordPress/gutenberg/pull/61961)) +- PostComments editor component. ([61964](https://github.com/WordPress/gutenberg/pull/61964)) +- PostDiscussionPanel editor component. ([61966](https://github.com/WordPress/gutenberg/pull/61966)) +- PostExcerptPanel editor component. ([61967](https://github.com/WordPress/gutenberg/pull/61967)) +- PostLockedModal editor component. ([61968](https://github.com/WordPress/gutenberg/pull/61968)) +- PostPendingStatus + PostPendingStatusCheck editor components. ([61970](https://github.com/WordPress/gutenberg/pull/61970)) +- PostPingbacks editor component. ([62035](https://github.com/WordPress/gutenberg/pull/62035)) +- PostPreviewButton editor component. ([62036](https://github.com/WordPress/gutenberg/pull/62036)) +- Storybook: Add badges based on `tags`. ([61111](https://github.com/WordPress/gutenberg/pull/61111)) +- Update PostFormat, PostFormatCheck editor component documentation. ([61732](https://github.com/WordPress/gutenberg/pull/61732)) +- Update block.json file with correct links. ([61880](https://github.com/WordPress/gutenberg/pull/61880)) +- Update link to architecture key concepts. ([61965](https://github.com/WordPress/gutenberg/pull/61965)) +- Update links to correct lodash website. ([62188](https://github.com/WordPress/gutenberg/pull/62188)) +- Update plugin-document-setting-panel.md. ([61782](https://github.com/WordPress/gutenberg/pull/61782)) +- Update tutorial.md. ([62054](https://github.com/WordPress/gutenberg/pull/62054)) + + +### Code Quality + +- Add curly brace autofix commit to `.git-blame-ignore-revs`. ([62144](https://github.com/WordPress/gutenberg/pull/62144)) +- Add eslint rule for curly brace presence in JSX. ([62026](https://github.com/WordPress/gutenberg/pull/62026)) +- Blocks: Remove pipe usage and dependency on compose. ([62127](https://github.com/WordPress/gutenberg/pull/62127)) +- Clean up packages build-types when cleaning types. ([61939](https://github.com/WordPress/gutenberg/pull/61939)) +- Command Palette: Remove unused URL parameter. ([61783](https://github.com/WordPress/gutenberg/pull/61783)) +- Commands: Unify the editor context between post and site editors. ([61862](https://github.com/WordPress/gutenberg/pull/61862)) +- Dataviews: Remove unused dependencies. ([62010](https://github.com/WordPress/gutenberg/pull/62010)) +- Distraction Free: Unify the header animation. ([62167](https://github.com/WordPress/gutenberg/pull/62167)) +- Editor: Move editor toggle commands to the editor package. ([62093](https://github.com/WordPress/gutenberg/pull/62093)) +- Editor: Move the InterfaceSkeleton to the editor package. ([62118](https://github.com/WordPress/gutenberg/pull/62118)) +- Editor: Move the resizing of the editor to the EditorCanvas component. ([61896](https://github.com/WordPress/gutenberg/pull/61896)) +- Editor: Remove extra div container and unify the container between post and site editors. ([62016](https://github.com/WordPress/gutenberg/pull/62016)) +- Editor: Remove obsolete `listViewLabel` prop from DocumentTools. ([62032](https://github.com/WordPress/gutenberg/pull/62032)) +- Editor: Remove useless props from InserterSidebar component. ([62103](https://github.com/WordPress/gutenberg/pull/62103)) +- Editor: Unify the MediaUpload hook between post and site editors. ([62085](https://github.com/WordPress/gutenberg/pull/62085)) +- Editor: Unify the content area of the post and site editors. ([61860](https://github.com/WordPress/gutenberg/pull/61860)) +- Fix: React compiler error on button. ([61958](https://github.com/WordPress/gutenberg/pull/61958)) +- Fix: Remove unused css block on patterns page. ([62058](https://github.com/WordPress/gutenberg/pull/62058)) +- Fix: Remove unused css code from the navigation screen. ([62060](https://github.com/WordPress/gutenberg/pull/62060)) +- Fix: Some jsdoc return types on edit site selector. ([62061](https://github.com/WordPress/gutenberg/pull/62061)) +- Improve distclean script. ([62019](https://github.com/WordPress/gutenberg/pull/62019)) +- Interactivity API: Move all utils inside `utils.ts`. ([61721](https://github.com/WordPress/gutenberg/pull/61721)) +- Interactivity API: Move init.js to TypeScript. ([61723](https://github.com/WordPress/gutenberg/pull/61723)) +- Make onPatternCategorySelection private. ([62130](https://github.com/WordPress/gutenberg/pull/62130)) +- Remove useless clsx calls. ([61969](https://github.com/WordPress/gutenberg/pull/61969)) +- Rename backport-changelog/6279.md to backport-changelog/6.6/6279.md. ([61894](https://github.com/WordPress/gutenberg/pull/61894)) +- Update: Remove unused components. ([61955](https://github.com/WordPress/gutenberg/pull/61955)) +- end-to-end Tests: Fix React warnings triggered by test plugins. ([61935](https://github.com/WordPress/gutenberg/pull/61935)) + +#### Components +- CustomSelectControl: Fix `menuProps` mutation. ([62149](https://github.com/WordPress/gutenberg/pull/62149)) +- Fix remaining warning in ColorPanelDropdown. ([61933](https://github.com/WordPress/gutenberg/pull/61933)) +- Make the `ProgressBar` public. ([61062](https://github.com/WordPress/gutenberg/pull/61062)) +- Remove reduceMotion utility. ([61963](https://github.com/WordPress/gutenberg/pull/61963)) +- SlotFills: Use state for registry initialization. ([61802](https://github.com/WordPress/gutenberg/pull/61802)) +- Style Book: Use state to initialize examples. ([61848](https://github.com/WordPress/gutenberg/pull/61848)) +- Tooltip: Fix Ariakit tooltip store usage. ([61858](https://github.com/WordPress/gutenberg/pull/61858)) +- `ProgressBar`: Simplify default `width` implementation and make it more easily overridable. ([61976](https://github.com/WordPress/gutenberg/pull/61976)) + +#### Block Editor +- Fix `ZoomOutModeInserters` dependencies. ([61908](https://github.com/WordPress/gutenberg/pull/61908)) +- Fix wrapper props mutation in BlockListBlock. ([61789](https://github.com/WordPress/gutenberg/pull/61789)) +- Remove some utility functions. ([61784](https://github.com/WordPress/gutenberg/pull/61784)) +- Shadows: Unlock private components and hooks at the file level. ([61790](https://github.com/WordPress/gutenberg/pull/61790)) +- Unlock private setting keys at the file level. ([61813](https://github.com/WordPress/gutenberg/pull/61813)) +- Unlock the private 'kebabCase' function at a file level. ([60755](https://github.com/WordPress/gutenberg/pull/60755)) +- useBlockInspectorAnimationSettings: Remove unnecessary deps. ([61822](https://github.com/WordPress/gutenberg/pull/61822)) + +#### Data Views +- DataViews: Full type the dataviews package. ([61854](https://github.com/WordPress/gutenberg/pull/61854)) +- DataViews: Remove non-used file. ([61853](https://github.com/WordPress/gutenberg/pull/61853)) +- DataViews: Remove unnecessary dependency for pattern fields memo. ([61870](https://github.com/WordPress/gutenberg/pull/61870)) +- DataViews: Type all the filters components. ([61795](https://github.com/WordPress/gutenberg/pull/61795)) +- DataViews: Type the BulkActionsToolbar component. ([61673](https://github.com/WordPress/gutenberg/pull/61673)) +- DataViews: Type the ViewActions component. ([61729](https://github.com/WordPress/gutenberg/pull/61729)) +- DataViews: Type the ViewTable component. ([61682](https://github.com/WordPress/gutenberg/pull/61682)) + +#### Block Library +- Added unit test for post excerpt block render function. ([43451](https://github.com/WordPress/gutenberg/pull/43451)) +- Avoid using component naming conventions for non-component code. ([61793](https://github.com/WordPress/gutenberg/pull/61793)) +- Button: Fix ESLint warning. ([62126](https://github.com/WordPress/gutenberg/pull/62126)) +- Remove CSS hack for Internet Explorer 11. ([62043](https://github.com/WordPress/gutenberg/pull/62043)) +- Remove useless styles. ([62017](https://github.com/WordPress/gutenberg/pull/62017)) +- Search Block: Fix `borderRadius` mutation. ([61794](https://github.com/WordPress/gutenberg/pull/61794)) #### Site Editor -- Redirect `/wp_template_part/all` to `/patterns`. ([61446](https://github.com/WordPress/gutenberg/pull/61446)) -- Moves "Patterns" command to site editor main navigation. ([61416](https://github.com/WordPress/gutenberg/pull/61416)) +- History: Add getLocationWithParams method. ([61823](https://github.com/WordPress/gutenberg/pull/61823)) +- Navigation Focus Mode: Remove leftover code. ([61897](https://github.com/WordPress/gutenberg/pull/61897)) +- Remove useless onClick handler. ([61902](https://github.com/WordPress/gutenberg/pull/61902)) +- Update to use the EditorInterface component from the editor package. ([62146](https://github.com/WordPress/gutenberg/pull/62146)) -#### Navigation Menus -- Remove default entry into Navigation Menu focus mode but retain ability to access via "Edit". ([61275](https://github.com/WordPress/gutenberg/pull/61275)) +#### Block hooks +- Navigation block: Check for insert_hooked_blocks_into_rest_response i…. ([62134](https://github.com/WordPress/gutenberg/pull/62134)) +- Navigation block: Check for update_ignored_hooked_blocks_postmeta in core. ([61903](https://github.com/WordPress/gutenberg/pull/61903)) + +#### Font Library +- Font Library Modal: Remove some contexts. ([62042](https://github.com/WordPress/gutenberg/pull/62042)) + +#### Post Editor +- Template Actions: Fix console error when resetting template. ([61921](https://github.com/WordPress/gutenberg/pull/61921)) + +#### Global Styles +- Components: Fix React Warning triggers by the new JSX transform. ([61917](https://github.com/WordPress/gutenberg/pull/61917)) + +#### Interactivity API +- Interactivity API : Refactor interactivity-router to TS. ([61730](https://github.com/WordPress/gutenberg/pull/61730)) + +#### CSS & Styling +- Fix editor view mode canvas shadow. ([61688](https://github.com/WordPress/gutenberg/pull/61688)) + + +### Tools + +- Build: Use globalThis over process.env and enable TS lib checking. ([61486](https://github.com/WordPress/gutenberg/pull/61486)) + +#### Testing +- E2E: Fix canvas waiter in visitSiteEditor. ([61816](https://github.com/WordPress/gutenberg/pull/61816)) +- PaletteEdit: Fix another flaky test. ([61818](https://github.com/WordPress/gutenberg/pull/61818)) +- PaletteEdit: Fix flaky test. ([61791](https://github.com/WordPress/gutenberg/pull/61791)) +- Shadow: Add unit tests for shadow support. ([60063](https://github.com/WordPress/gutenberg/pull/60063)) +- Skip flaky 'Zoom out' end-to-end test. ([61925](https://github.com/WordPress/gutenberg/pull/61925)) +- Synced Pattern: Wait for pattern creation in end-to-end tests. ([62174](https://github.com/WordPress/gutenberg/pull/62174)) +- Tests: Change how directives processing gets disabled. ([62095](https://github.com/WordPress/gutenberg/pull/62095)) +- Workflows: Try a backport changelog. ([61785](https://github.com/WordPress/gutenberg/pull/61785)) + +#### Build Tooling +- Add 60 minute timeout to performance job. ([61957](https://github.com/WordPress/gutenberg/pull/61957)) +- Enable parallel processing for PHPCS sniffs. ([61700](https://github.com/WordPress/gutenberg/pull/61700)) +- Fix an issue causing wp-scripts commands to fail if the file path contained a space character. ([61748](https://github.com/WordPress/gutenberg/pull/61748)) +- React: Upgrade to the new JSX transform. ([61692](https://github.com/WordPress/gutenberg/pull/61692)) +- Workflows: Test to check for label and skip backport changelog. ([61808](https://github.com/WordPress/gutenberg/pull/61808)) + + +### Various + +- Inserter: Encapsulate styles for tablist and close button. ([61760](https://github.com/WordPress/gutenberg/pull/61760)) +- Update 'Add template' screen to prefer template_name label instead of singular_name. ([60367](https://github.com/WordPress/gutenberg/pull/60367)) +- Update: Move pattern actions to the editor package. [take 2]. ([61612](https://github.com/WordPress/gutenberg/pull/61612)) + +#### Global Styles +- Update copy for color variations from "Presets" to "Palettes". ([62147](https://github.com/WordPress/gutenberg/pull/62147)) #### Synced Patterns -- Add content only descriptions in dropdown menus for patterns and templates. ([61127](https://github.com/WordPress/gutenberg/pull/61127)) +- Remove `IS_GUTENBERG_PLUGIN` check to ensure pattern overrides ship in 6.6. ([62011](https://github.com/WordPress/gutenberg/pull/62011)) + +#### npm Packages +- Packages: Increase the minimum required Node.js version to v18.12.0. ([61930](https://github.com/WordPress/gutenberg/pull/61930)) + +#### Layout +- Update child layout selector to match core. ([61777](https://github.com/WordPress/gutenberg/pull/61777)) + +#### Components +- Introduce Combobox `expandOnFocus` property. ([61705](https://github.com/WordPress/gutenberg/pull/61705)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @akashdhawade2005: Fix: Block library README.md link. ([62081](https://github.com/WordPress/gutenberg/pull/62081)) +- @amitraj2203: Added Bluesky icon to the Social Icon Block. ([61372](https://github.com/WordPress/gutenberg/pull/61372)) +- @dbrian: Add JSDoc to PostVisibility, PostVisibilityCheck, and PostVisibilityLabel. ([61735](https://github.com/WordPress/gutenberg/pull/61735)) +- @gemkev: Update tutorial.md. ([62054](https://github.com/WordPress/gutenberg/pull/62054)) +- @kellenmace: Fix an issue causing wp-scripts commands to fail if the file path contained a space character. ([61748](https://github.com/WordPress/gutenberg/pull/61748)) +- @narenin: Fixed: Custom HTML Block should display content in LTR layout for all languages. ([62083](https://github.com/WordPress/gutenberg/pull/62083)) +- @nateinaction: Add documentation for the EditorProvider and ExperimentalEditorProvider components. ([61739](https://github.com/WordPress/gutenberg/pull/61739)) +- @paolopiaggio: Playwright end-to-end Utils: Add fullscreenMode option to createNewPost. ([61766](https://github.com/WordPress/gutenberg/pull/61766)) +- @sanjucta: Add docblock to PostTitle and PostTitleRaw component. ([61740](https://github.com/WordPress/gutenberg/pull/61740)) +- @vipul0425: Fix: The latest post block - post titles overlapping. ([61356](https://github.com/WordPress/gutenberg/pull/61356)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @abhi3315 @afercia @ajlende @akashdhawade2005 @akasunil @Aljullu @amitraj2203 @andrewserong @anton-vlasenko @anver @artemiomorales @carolinan @cbravobernal @colorful-tones @creativecoder @DaniGuardiola @DAreRodz @dbrian @draganescu @ellatrix @fabiankaegy @fullofcaffeine @gemkev @geriux @glendaviesnz @gziolo @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @kellenmace @kevin940726 @kt-12 @madhusudhand @Mamaduka @mattsherman @mcsf @michalczaplinski @mirka @narenin @nateinaction @ndiego @ntsekouras @oandregal @ockham @paolopiaggio @ramonjd @retrofox @richtabor @sanjucta @SantosGuillamot @scruffian @senadir @shail-mehta @sirreal @stokesman @t-hamano @talldan @taylorgorman @tellthemachines @tjcafferkey @twstokes @tyxla @vcanales @vipul0425 @westonruter @WunderBart @youknowriad + + + + += 18.4.1 = + +## Changelog + +### Bug fixes + +- Writing flow: fix paste for input fields ([61389](https://github.com/WordPress/gutenberg/pull/61389)) + + += 18.4.0 = + +## Changelog + +### Enhancements + +#### Layout +- Check child layout exists before generating classname. ([61392](https://github.com/WordPress/gutenberg/pull/61392)) +- Hide Image block resizer when inside a grid layout. ([61603](https://github.com/WordPress/gutenberg/pull/61603)) + +#### Grid interactivity +- Improve `max` attribute logic. ([61420](https://github.com/WordPress/gutenberg/pull/61420)) +- Improve how grid resizer handles 0-width and 0-height cells. ([61423](https://github.com/WordPress/gutenberg/pull/61423)) +- Show grid visualizer when block inspector is closed. ([61429](https://github.com/WordPress/gutenberg/pull/61429)) +- Stabilise grid layout visualiser and resizer. ([61640](https://github.com/WordPress/gutenberg/pull/61640)) + +#### Global Styles +- Add aspect ratio presets support via theme.json. ([47271](https://github.com/WordPress/gutenberg/pull/47271), [61774](https://github.com/WordPress/gutenberg/pull/61774)) +- Background images: Remove required "file" prop. ([61387](https://github.com/WordPress/gutenberg/pull/61387), [61469](https://github.com/WordPress/gutenberg/pull/61469)) +- Change "Solid" tab to "Color". ([61366](https://github.com/WordPress/gutenberg/pull/61366)) +- Improve panel title and description for palette. ([61365](https://github.com/WordPress/gutenberg/pull/61365)) +- Tweak palette panel spacing and empty message. ([61368](https://github.com/WordPress/gutenberg/pull/61368)) +- Update color variations. ([61334](https://github.com/WordPress/gutenberg/pull/61334)) #### Block Styles - Add extended version of register block style functions. ([61029](https://github.com/WordPress/gutenberg/pull/61029)) +### Post actions +- Improve success messages of some post actions. ([61539](https://github.com/WordPress/gutenberg/pull/61539)) +- Unify the list of available post type actions. ([61520](https://github.com/WordPress/gutenberg/pull/61520)) +- Don't export duplicatePostAction for now. ([61407](https://github.com/WordPress/gutenberg/pull/61407)) + +#### Zoom Out +- Editor: Enable Zoom-out mode in the post editor. ([61293](https://github.com/WordPress/gutenberg/pull/61293)) +- Keep original viewport width (single scale). ([61424](https://github.com/WordPress/gutenberg/pull/61424)) +- Open inserter sidebar when clicking on inserter buttons on zoom-out mode. ([61434](https://github.com/WordPress/gutenberg/pull/61434)) +- Remove experimental zoom out control. ([61509](https://github.com/WordPress/gutenberg/pull/61509)) +- Zoomed Out View: Don't close the inserter. ([61004](https://github.com/WordPress/gutenberg/pull/61004)) + +#### Components +- Do not render FormTokenField label when not defined. ([61336](https://github.com/WordPress/gutenberg/pull/61336)) +- Placeholder: Tweak placeholder style. ([61590](https://github.com/WordPress/gutenberg/pull/61590)) +- Add content only descriptions in dropdown menus for patterns and templates. ([61127](https://github.com/WordPress/gutenberg/pull/61127)) + +#### Block Library +- List Block: Add block class name to the list block. ([56469](https://github.com/WordPress/gutenberg/pull/56469)) +- Embeds Block: Add Bluesky variation. ([61352](https://github.com/WordPress/gutenberg/pull/61352)) +- Site Logo Block: Add setting labels via the 'register_setting' method. ([61351](https://github.com/WordPress/gutenberg/pull/61351)) + +#### Block Editor +- Make `BlockPopover` component public. ([61529](https://github.com/WordPress/gutenberg/pull/61529)) +- Only add the selected pattern category in metadata during insertion. ([61557](https://github.com/WordPress/gutenberg/pull/61557)) +- Add a keyboard shortcut to create group from the selected blocks. ([46972](https://github.com/WordPress/gutenberg/pull/46972)) +- Enhance block outlines and selection interactions. ([60757](https://github.com/WordPress/gutenberg/pull/60757)) +- Tiny tweak to position close button properly in the inserter. ([61461](https://github.com/WordPress/gutenberg/pull/61461)) + #### Editor - Editor: Unify Header component. ([61273](https://github.com/WordPress/gutenberg/pull/61273)) - Editor: Unify the sidebar between the post and site editors. ([61507](https://github.com/WordPress/gutenberg/pull/61507)) - Editor: Update and simplify the Post Summary and Post Card section in the document sidebar. ([61624](https://github.com/WordPress/gutenberg/pull/61624)) -- Improve success messages of some post actions. ([61539](https://github.com/WordPress/gutenberg/pull/61539)) - Try: Improve date-wrapping in prepublish flow. ([61490](https://github.com/WordPress/gutenberg/pull/61490)) - Update: Implement the new discussion panel design. ([61357](https://github.com/WordPress/gutenberg/pull/61357)) -### New APIs +#### Post Editor +- Add global styles to settings using existing context code. ([61556](https://github.com/WordPress/gutenberg/pull/61556)) +- Display a notice after moving a post into the trash. ([61670](https://github.com/WordPress/gutenberg/pull/61670)) +- Simplify Post Publish Flow status term. ([61386](https://github.com/WordPress/gutenberg/pull/61386)) -#### Extensibility -- Make `BlockPopover` component public. ([61529](https://github.com/WordPress/gutenberg/pull/61529)) +#### Site Editor +- Redirect `/wp_template_part/all` to `/patterns`. ([61446](https://github.com/WordPress/gutenberg/pull/61446)) +- Moves "Patterns" command to site editor main navigation. ([61416](https://github.com/WordPress/gutenberg/pull/61416)) +- Adds "Template Parts" command to site editor. ([61287](https://github.com/WordPress/gutenberg/pull/61287)) +- Show pin/unpin button on the site editor plugin sidebar. ([61448](https://github.com/WordPress/gutenberg/pull/61448)) +- Remove default entry into Navigation Menu focus mode but retain ability to access via "Edit". ([61275](https://github.com/WordPress/gutenberg/pull/61275)) + +#### Data Views +- Add bulk actions toolbar. ([59714](https://github.com/WordPress/gutenberg/pull/59714)) +- Align list and table layout visuals. ([61157](https://github.com/WordPress/gutenberg/pull/61157)) +- Add actions to list layout. ([60805](https://github.com/WordPress/gutenberg/pull/60805)) +- Make pattern preview click area larger. ([61250](https://github.com/WordPress/gutenberg/pull/61250)) ### Bug Fixes @@ -102,6 +573,22 @@ - GridItemResizer: Fix resizing when List View is open. ([61643](https://github.com/WordPress/gutenberg/pull/61643)) - Only show grid resizer if grid block allows resizing on children. ([61552](https://github.com/WordPress/gutenberg/pull/61552)) +#### Global Styles +- Background image: Explicitly set background repeat value in user styles. ([61526](https://github.com/WordPress/gutenberg/pull/61526)) +- Background image: Size controls should show when an image is set. ([61388](https://github.com/WordPress/gutenberg/pull/61388)) +- Make sure to replace all instances of `:Where(body)` instead of just …. ([61602](https://github.com/WordPress/gutenberg/pull/61602)) +- Reduce specificity of global styles body margin reset rule. ([61340](https://github.com/WordPress/gutenberg/pull/61340)) +- Remove Post Template background override. ([61545](https://github.com/WordPress/gutenberg/pull/61545)) + +#### Patterns +- Fix blocks in unsynced patterns can enable overrides. ([61639](https://github.com/WordPress/gutenberg/pull/61639)) +- Revert "Use contentOnly locking for pattern block, remove hard-coded block check in block inspector". ([61517](https://github.com/WordPress/gutenberg/pull/61517)) + +#### List View +- Account for text fields in shortcut handler. ([61583](https://github.com/WordPress/gutenberg/pull/61583)) +- Add a special case for shortcuts coming from modals. ([61606](https://github.com/WordPress/gutenberg/pull/61606)) +- Standardize List View feature name to use title case capitalization. ([61535](https://github.com/WordPress/gutenberg/pull/61535)) + #### Zoom Out - Block editor: Scroll block into view on insert. ([61418](https://github.com/WordPress/gutenberg/pull/61418)) - Fix double scrollbars in site editor with zoom out view enabled. ([61548](https://github.com/WordPress/gutenberg/pull/61548)) @@ -113,20 +600,18 @@ - Pass the section root ID to the inserter. ([61464](https://github.com/WordPress/gutenberg/pull/61464)) - Zoom-out: Fix iframe ref error. ([61200](https://github.com/WordPress/gutenberg/pull/61200)) +#### Components +- Fix inconsistent complementary header styles. ([61331](https://github.com/WordPress/gutenberg/pull/61331)) +- Fix sticking “Reset” option in `ToolsPanel`. ([60621](https://github.com/WordPress/gutenberg/pull/60621)) +- RadioControl: Fix shrinking radio controls. ([61476](https://github.com/WordPress/gutenberg/pull/61476)) + #### Block Library -- Add list item wrapper to social links when used in navigation block. ([61396](https://github.com/WordPress/gutenberg/pull/61396)) -- Fix: Remove font weight on toolbar tab button - #61254. ([61308](https://github.com/WordPress/gutenberg/pull/61308)) -- Fix: Time to Read block showing "this block has encountered an error" - #61459. ([61614](https://github.com/WordPress/gutenberg/pull/61614)) +- Navigation Block: Add list item wrapper to social links when used in navigation block. ([61396](https://github.com/WordPress/gutenberg/pull/61396)) +- HTML Block: Remove font weight on toolbar tab button - #61254. ([61308](https://github.com/WordPress/gutenberg/pull/61308)) +- Time to Read Block: Fix "this block has encountered an error" - #61459. ([61614](https://github.com/WordPress/gutenberg/pull/61614)) - Image Block: Enable crop action when image has a link. ([61470](https://github.com/WordPress/gutenberg/pull/61470)) - Shortcode Block: Fix layout margin override. ([55028](https://github.com/WordPress/gutenberg/pull/55028)) -#### Global Styles -- Background image: Explicitly set background repeat value in user styles. ([61526](https://github.com/WordPress/gutenberg/pull/61526)) -- Background image: Size controls should show when an image is set. ([61388](https://github.com/WordPress/gutenberg/pull/61388)) -- Make sure to replace all instances of `:Where(body)` instead of just …. ([61602](https://github.com/WordPress/gutenberg/pull/61602)) -- Reduce specificity of global styles body margin reset rule. ([61340](https://github.com/WordPress/gutenberg/pull/61340)) -- Remove Post Template background override. ([61545](https://github.com/WordPress/gutenberg/pull/61545)) - #### Block Editor - Editor styles: Fix cache (by wrapper selector). ([61397](https://github.com/WordPress/gutenberg/pull/61397)) - Fix Truncate component for long unbreakable text. ([61137](https://github.com/WordPress/gutenberg/pull/61137)) @@ -134,20 +619,9 @@ - Fix: The issue of appender button not clickable in row/stack group. ([61585](https://github.com/WordPress/gutenberg/pull/61585)) - Writing Flow/Rich Text: Unify split logic. ([54543](https://github.com/WordPress/gutenberg/pull/54543)) -#### List View -- Account for text fields in shortcut handler. ([61583](https://github.com/WordPress/gutenberg/pull/61583)) -- Add a special case for shortcuts coming from modals. ([61606](https://github.com/WordPress/gutenberg/pull/61606)) -- Standardize List View feature name to use title case capitalization. ([61535](https://github.com/WordPress/gutenberg/pull/61535)) - -#### Components -- Fix inconsistent complementary header styles. ([61331](https://github.com/WordPress/gutenberg/pull/61331)) -- Fix sticking “Reset” option in `ToolsPanel`. ([60621](https://github.com/WordPress/gutenberg/pull/60621)) -- RadioControl: Fix shrinking radio controls. ([61476](https://github.com/WordPress/gutenberg/pull/61476)) - -#### Interactivity API -- Interactivity API: Allow multiple event handlers for the same type with `data-wp-on-document` and `data-wp-on-window`. ([61009](https://github.com/WordPress/gutenberg/pull/61009)) -- Interactivity API: Prevent empty namespace or different namespaces from killing the runtime. ([61409](https://github.com/WordPress/gutenberg/pull/61409)) -- Interactivity API: Prevent wrong written directives from killing the runtime. ([61249](https://github.com/WordPress/gutenberg/pull/61249)) +#### Post Editor +- Fix the 'usePaddingAppender' error. ([61500](https://github.com/WordPress/gutenberg/pull/61500)) +- Return an empty object when no fallback templates are found (wp/v2/templates/lookup). ([60925](https://github.com/WordPress/gutenberg/pull/60925)) #### Site Editor - Fix user capabilities check for the Site Editor. ([61444](https://github.com/WordPress/gutenberg/pull/61444)) @@ -155,42 +629,36 @@ - Trigger sidebar animations only on cross-route navigations. ([61402](https://github.com/WordPress/gutenberg/pull/61402)) - Site Editor: Restore the hover zoom effect when hovering the editor frame. ([61647](https://github.com/WordPress/gutenberg/pull/61647)) -#### Data Views -- Fix regression on keyboard navigation. ([61478](https://github.com/WordPress/gutenberg/pull/61478)) -- fix: Improve dataview types. ([61586](https://github.com/WordPress/gutenberg/pull/61586)) - -#### Patterns -- Fix blocks in unsynced patterns can enable overrides. ([61639](https://github.com/WordPress/gutenberg/pull/61639)) - -#### Synced Patterns -- Revert "Use contentOnly locking for pattern block, remove hard-coded block check in block inspector". ([61517](https://github.com/WordPress/gutenberg/pull/61517)) - #### Widgets Editor - Hide the close button on the inserter for widgets editor. ([61510](https://github.com/WordPress/gutenberg/pull/61510)) -#### Post Editor -- Edit Post: Fix the 'usePaddingAppender' error. ([61500](https://github.com/WordPress/gutenberg/pull/61500)) +#### Data Views +- Fix regression on keyboard navigation. ([61478](https://github.com/WordPress/gutenberg/pull/61478)) +- Improve dataview types. ([61586](https://github.com/WordPress/gutenberg/pull/61586)) +#### Interactivity API +- Interactivity API: Allow multiple event handlers for the same type with `data-wp-on-document` and `data-wp-on-window`. ([61009](https://github.com/WordPress/gutenberg/pull/61009)) +- Interactivity API: Prevent empty namespace or different namespaces from killing the runtime. ([61409](https://github.com/WordPress/gutenberg/pull/61409)) +- Interactivity API: Prevent wrong written directives from killing the runtime. ([61249](https://github.com/WordPress/gutenberg/pull/61249)) -### Accessibility -#### Block Editor -- Focus currently selected block when entering canvas. ([61472](https://github.com/WordPress/gutenberg/pull/61472)) -- Focus inserter toggle when closing the inserter sidebar. ([61467](https://github.com/WordPress/gutenberg/pull/61467)) +### Accessibility #### Components - ComboboxControl supports disabled items. ([61294](https://github.com/WordPress/gutenberg/pull/61294)) - Remove usage of aria-details from InputControl and BaseControl. ([61203](https://github.com/WordPress/gutenberg/pull/61203)) -#### Post Editor -- Post Actions: Correctly disable dropdown trigger. ([61625](https://github.com/WordPress/gutenberg/pull/61625)) - #### Block Library - Fix the RRS block placeholder labeling and improve spacing. ([61576](https://github.com/WordPress/gutenberg/pull/61576)) -#### Zoom Out +#### Block Editor +- Focus currently selected block when entering canvas. ([61472](https://github.com/WordPress/gutenberg/pull/61472)) +- Focus inserter toggle when closing the inserter sidebar. ([61467](https://github.com/WordPress/gutenberg/pull/61467)) - Inserter: Add close button. ([61421](https://github.com/WordPress/gutenberg/pull/61421)) +#### Post Editor +- Post Actions: Correctly disable dropdown trigger. ([61625](https://github.com/WordPress/gutenberg/pull/61625)) + ### Performance @@ -201,14 +669,6 @@ - Revert "useBlockSync: Remove isControlled effect". ([61480](https://github.com/WordPress/gutenberg/pull/61480)) -### Experiments - -#### Layout -- Grid interactivity: Improve `max` attribute logic. ([61420](https://github.com/WordPress/gutenberg/pull/61420)) -- Grid interactivity: Improve how grid resizer handles 0-width and 0-height cells. ([61423](https://github.com/WordPress/gutenberg/pull/61423)) -- Grid interactivity: Show grid visualizer when block inspector is closed. ([61429](https://github.com/WordPress/gutenberg/pull/61429)) - - ### Documentation - Update old document URLs to new ones. ([61595](https://github.com/WordPress/gutenberg/pull/61595)) @@ -237,48 +697,46 @@ ### Code Quality - PHP load: move rest template controller 6.6 import to "REST" area. ([61564](https://github.com/WordPress/gutenberg/pull/61564)) -- Background images: Remove lingering "file" prop. ([61469](https://github.com/WordPress/gutenberg/pull/61469)) -- Replace classnames with clsx. ([61138](https://github.com/WordPress/gutenberg/pull/61138)) -- Fix JS lint error for `clsx`. ([61380](https://github.com/WordPress/gutenberg/pull/61380)) - -#### Data Views -- DataViews: Expand typing more components. ([61654](https://github.com/WordPress/gutenberg/pull/61654)) -- Add end-to-end tests for keyboard interactions in DataViews ListView. ([61648](https://github.com/WordPress/gutenberg/pull/61648)) -- DataViews: Add types to the ViewGrid component. ([61667](https://github.com/WordPress/gutenberg/pull/61667)) -- DataViews: Type the BulkActions component. ([61666](https://github.com/WordPress/gutenberg/pull/61666)) -- DataViews: Type the ItemActions component. ([61400](https://github.com/WordPress/gutenberg/pull/61400)) -- DataViews: Type the ViewList component. ([61246](https://github.com/WordPress/gutenberg/pull/61246)) -- Remove `onActionPerformed` & `onActionStart` from the ActionModal API. ([61659](https://github.com/WordPress/gutenberg/pull/61659)) +- Replace classnames with clsx. ([61138](https://github.com/WordPress/gutenberg/pull/61138), [61380](https://github.com/WordPress/gutenberg/pull/61380)) -#### Block bindings +#### Block Bindings - Remove not needed breaks in `gutenberg_block_bindings_replace_html`. ([61660](https://github.com/WordPress/gutenberg/pull/61660)) - Simplify the HTML replacement logic until the HTML API is ready. ([61236](https://github.com/WordPress/gutenberg/pull/61236)) -#### Post Editor -- Edit post: Do not consider sidebars mutually exclusive. ([61468](https://github.com/WordPress/gutenberg/pull/61468)) -- Editor: Move the sidebar component to the editor package. ([61497](https://github.com/WordPress/gutenberg/pull/61497)) +#### Patterns +- Pattern overrides: Use block binding editing API. ([60721](https://github.com/WordPress/gutenberg/pull/60721)) #### Components - Assess stabilization of `Theme`. ([61077](https://github.com/WordPress/gutenberg/pull/61077)) - Upgrade @types/react package and @types/react-dom. ([60796](https://github.com/WordPress/gutenberg/pull/60796)) - Fix problem with gradient-parser types. ([61679](https://github.com/WordPress/gutenberg/pull/61679)) -#### Interactivity API -- Interactivity API: Add types for warn helper. ([61687](https://github.com/WordPress/gutenberg/pull/61687)) -- Interactivity: Strict type checking. ([59865](https://github.com/WordPress/gutenberg/pull/59865)) - #### Block Editor - Improve `LineHeightControl` unit tests. ([61337](https://github.com/WordPress/gutenberg/pull/61337)) +#### Editor +- Move the starter template options to the editor package. ([61665](https://github.com/WordPress/gutenberg/pull/61665)) + +#### Post Editor +- Edit post: Do not consider sidebars mutually exclusive. ([61468](https://github.com/WordPress/gutenberg/pull/61468)) +- Editor: Move the sidebar component to the editor package. ([61497](https://github.com/WordPress/gutenberg/pull/61497)) + #### Site Editor - Site Editor sidebar: Provide explicit backPaths, remove the getBackPath helper. ([61286](https://github.com/WordPress/gutenberg/pull/61286)) -#### Patterns -- Pattern overrides: Use block binding editing API. ([60721](https://github.com/WordPress/gutenberg/pull/60721)) +#### Data Views +- Expand typing more components. ([61654](https://github.com/WordPress/gutenberg/pull/61654)) +- Add end-to-end tests for keyboard interactions in DataViews ListView. ([61648](https://github.com/WordPress/gutenberg/pull/61648)) +- Add types to the ViewGrid component. ([61667](https://github.com/WordPress/gutenberg/pull/61667)) +- Type the BulkActions component. ([61666](https://github.com/WordPress/gutenberg/pull/61666)) +- Type the ItemActions component. ([61400](https://github.com/WordPress/gutenberg/pull/61400)) +- Type the ViewList component. ([61246](https://github.com/WordPress/gutenberg/pull/61246)) +- Remove `onActionPerformed` & `onActionStart` from the ActionModal API. ([61659](https://github.com/WordPress/gutenberg/pull/61659)) + +#### Interactivity API +- Add types for warn helper. ([61687](https://github.com/WordPress/gutenberg/pull/61687)) +- Enable strict type checking. ([59865](https://github.com/WordPress/gutenberg/pull/59865)) -#### Editor -- Editor: Move the starter template options to the editor package. ([61665](https://github.com/WordPress/gutenberg/pull/61665)) -- Post Actions: Don't export duplicatePostAction for now. ([61407](https://github.com/WordPress/gutenberg/pull/61407)) ### Tools @@ -303,7 +761,7 @@ - build:package-types: Run silently to reduce user confusion. ([61530](https://github.com/WordPress/gutenberg/pull/61530)) - GitHub Actions: Fix PHP file change detection filter pattern. ([61183](https://github.com/WordPress/gutenberg/pull/61183)) - Dedupe packages. ([61532](https://github.com/WordPress/gutenberg/pull/61532)) -- Patch react-autosize-textarea for updated types. ([61570](https://github.com/WordPress/gutenberg/pull/61570)) +- Patch `react-autosize-textarea` for updated types. ([61570](https://github.com/WordPress/gutenberg/pull/61570)) - Upgrade @use-gesture/react. ([61503](https://github.com/WordPress/gutenberg/pull/61503)) - Upgrade framer-motion. ([61572](https://github.com/WordPress/gutenberg/pull/61572)) @@ -328,6 +786,8 @@ The following contributors merged PRs in this release: @aaronrobertshaw @afercia @ajlende @amitraj2203 @anton-vlasenko @artemiomorales @bacoords @carolinan @cbravobernal @colinduwe @DaniGuardiola @DAreRodz @desrosj @draganescu @ellatrix @fullofcaffeine @geriux @getdave @gigitux @hbhalodia @jameskoster @jasmussen @jeryj @jffng @johnhooks @jorgefilipecosta @jpstevens @jsnajdr @juanmaguitar @kevin940726 @kovshenin @MaggieCabrera @Mamaduka @mcsf @mrmurphy @ndiego @noisysocks @ntsekouras @oandregal @ramonjd @retrofox @richtabor @ryelle @SantosGuillamot @scheinercc @scruffian @shail-mehta @sirreal @stokesman @StyleShit @swissspidy @t-hamano @talldan @tellthemachines @tyxla @vipul0425 @WunderBart @youknowriad + + = 18.3.0 = ## Changelog @@ -918,7 +1378,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@tellthemachines @arthur791004 +@tellthemachines @arthur791004 = 18.1.1 = @@ -932,7 +1392,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@okmttdhr @ntsekouras +@okmttdhr @ntsekouras = 18.1.0 = @@ -1963,7 +2423,7 @@ The following contributors merged PRs in this release: - Rich text: Fix typing into empty flex element. ([59473](https://github.com/WordPress/gutenberg/pull/59473)) - URLPopover: Fix a problem with the layout of link settings. ([58906](https://github.com/WordPress/gutenberg/pull/58906)) - Fix issue with appender jumping when creating a new pattern. ([59582](https://github.com/WordPress/gutenberg/pull/59582)) -- Allow event bubbling even if keydown is passed. ([59474](https://github.com/WordPress/gutenberg/pull/59474)) +- Allow event bubbling even if keydown is passed. ([59474](https://github.com/WordPress/gutenberg/pull/59474)) #### Custom Fields - Block Bindings: Fix console error when selecting a bound block. ([59598](https://github.com/WordPress/gutenberg/pull/59598)) @@ -2884,7 +3344,7 @@ The following contributors merged PRs in this release: - Docs: Copy and formatting edits for the "Static or Dynamic rendering" guide. ([58681](https://github.com/WordPress/gutenberg/pull/58681)) - Docs: Copy and formatting edits for the "The block in the Editor" guide. ([58697](https://github.com/WordPress/gutenberg/pull/58697)) - Docs: Copy and formatting edits for the "The block wrapper" guide. ([58704](https://github.com/WordPress/gutenberg/pull/58704)) -- Docs: Copy and formatting edits for the "Working with Javascript for the Block Editor" guide. ([58651](https://github.com/WordPress/gutenberg/pull/58651)) +- Docs: Copy and formatting edits for the "Working with JavaScript for the Block Editor" guide. ([58651](https://github.com/WordPress/gutenberg/pull/58651)) - Docs: Copy and formatting edits for the "block.json" guide. ([58732](https://github.com/WordPress/gutenberg/pull/58732)) - Docs: Copy edits and list formatting for main Block Editor Handbook readme. ([58652](https://github.com/WordPress/gutenberg/pull/58652)) - Docs: Fix list formatting and some grammar in the Entities explanation doc. ([58655](https://github.com/WordPress/gutenberg/pull/58655)) @@ -3103,7 +3563,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@ramonjd +@ramonjd = 17.7.0-rc.1 = @@ -3458,7 +3918,7 @@ The following contributors merged PRs in this release: - Docs: Copy and formatting edits for the "Static or Dynamic rendering" guide. ([58681](https://github.com/WordPress/gutenberg/pull/58681)) - Docs: Copy and formatting edits for the "The block in the Editor" guide. ([58697](https://github.com/WordPress/gutenberg/pull/58697)) - Docs: Copy and formatting edits for the "The block wrapper" guide. ([58704](https://github.com/WordPress/gutenberg/pull/58704)) -- Docs: Copy and formatting edits for the "Working with Javascript for the Block Editor" guide. ([58651](https://github.com/WordPress/gutenberg/pull/58651)) +- Docs: Copy and formatting edits for the "Working with JavaScript for the Block Editor" guide. ([58651](https://github.com/WordPress/gutenberg/pull/58651)) - Docs: Copy and formatting edits for the "block.json" guide. ([58732](https://github.com/WordPress/gutenberg/pull/58732)) - Docs: Copy edits and list formatting for main Block Editor Handbook readme. ([58652](https://github.com/WordPress/gutenberg/pull/58652)) - Docs: Fix list formatting and some grammar in the Entities explanation doc. ([58655](https://github.com/WordPress/gutenberg/pull/58655)) @@ -3685,7 +4145,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@dd32 +@dd32 = 17.6.2 = @@ -4203,7 +4663,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@andrewserong @t-hamano @luisherranz @pbking +@andrewserong @t-hamano @luisherranz @pbking = 17.6.0-rc.1 = @@ -4630,7 +5090,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@fullofcaffeine +@fullofcaffeine @@ -4647,7 +5107,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@youknowriad @fullofcaffeine +@youknowriad @fullofcaffeine @@ -4920,7 +5380,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@glendaviesnz +@glendaviesnz @@ -4952,7 +5412,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@ellatrix +@ellatrix @@ -5027,7 +5487,7 @@ The following contributors merged PRs in this release: - Gallery: Hide some controls when multi-editing blocks. ([57378](https://github.com/WordPress/gutenberg/pull/57378)) - Image Block: Get lightbox trigger button ref via data-wp-init. ([57089](https://github.com/WordPress/gutenberg/pull/57089)) - Image: Hide caption control when multi-editing images. ([57357](https://github.com/WordPress/gutenberg/pull/57357)) -- Video: Hide some controls when multi-editing blocks. ([57375](https://github.com/WordPress/gutenberg/pull/57375)) +- Video: Hide some controls when multi-editing blocks. ([57375](https://github.com/WordPress/gutenberg/pull/57375)) - Image Block: Fix deprecation when width/height attribute is number. ([57063](https://github.com/WordPress/gutenberg/pull/57063)) #### Patterns @@ -5040,7 +5500,7 @@ The following contributors merged PRs in this release: - Fix image upload bug. ([57040](https://github.com/WordPress/gutenberg/pull/57040)) - Reduce clearance around the Frame in the site editor. ([57023](https://github.com/WordPress/gutenberg/pull/57023)) - Swap Template: Show the right templates for the right post type. ([57149](https://github.com/WordPress/gutenberg/pull/57149)) -- Save Button: Fix the translation of the Activate button. ([57147](https://github.com/WordPress/gutenberg/pull/57147)) +- Save Button: Fix the translation of the Activate button. ([57147](https://github.com/WordPress/gutenberg/pull/57147)) - SlotFill: Allow contextual SlotFillProviders. ([56779](https://github.com/WordPress/gutenberg/pull/56779)) #### Post Editor @@ -5269,7 +5729,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@glendaviesnz +@glendaviesnz @@ -5958,7 +6418,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@jorgefilipecosta +@jorgefilipecosta = 17.2.0 = @@ -6202,7 +6662,7 @@ The following contributors merged PRs in this release: - Docs: Fundamentals of Block Development - File structure of a block. ([56551](https://github.com/WordPress/gutenberg/pull/56551)) - Docs: Fundamentals of Block Development - Registration of a block. ([56334](https://github.com/WordPress/gutenberg/pull/56334)) - Docs: Fundamentals of Block Development - The block wrapper. ([56596](https://github.com/WordPress/gutenberg/pull/56596)) -- Docs: Fundamentals of Block Development - Working with Javascript in the Block Editor. ([56553](https://github.com/WordPress/gutenberg/pull/56553)) +- Docs: Fundamentals of Block Development - Working with JavaScript in the Block Editor. ([56553](https://github.com/WordPress/gutenberg/pull/56553)) - Docs: Fundamentals of Block Development - block.json. ([56435](https://github.com/WordPress/gutenberg/pull/56435)) - Docs: Improve downloadBlob example. ([56225](https://github.com/WordPress/gutenberg/pull/56225)) - Documentation - Block Editor Handbook - Add end user documentation about Block Editor as a resource on the Landing Page of the Block Editor Handbook. ([49854](https://github.com/WordPress/gutenberg/pull/49854)) @@ -6531,7 +6991,7 @@ The following contributors merged PRs in this release: - Docs: Fundamentals of Block Development - File structure of a block. ([56551](https://github.com/WordPress/gutenberg/pull/56551)) - Docs: Fundamentals of Block Development - Registration of a block. ([56334](https://github.com/WordPress/gutenberg/pull/56334)) - Docs: Fundamentals of Block Development - The block wrapper. ([56596](https://github.com/WordPress/gutenberg/pull/56596)) -- Docs: Fundamentals of Block Development - Working with Javascript in the Block Editor. ([56553](https://github.com/WordPress/gutenberg/pull/56553)) +- Docs: Fundamentals of Block Development - Working with JavaScript in the Block Editor. ([56553](https://github.com/WordPress/gutenberg/pull/56553)) - Docs: Fundamentals of Block Development - block.json. ([56435](https://github.com/WordPress/gutenberg/pull/56435)) - Docs: Improve downloadBlob example. ([56225](https://github.com/WordPress/gutenberg/pull/56225)) - Documentation - Block Editor Handbook - Add end user documentation about Block Editor as a resource on the Landing Page of the Block Editor Handbook. ([49854](https://github.com/WordPress/gutenberg/pull/49854)) @@ -7617,7 +8077,7 @@ The following contributors merged PRs in this release: ## Changelog This copies the commits from the 16.7.1 patch release into the 16.8.0 main release. - + ### Tools #### Build Tooling @@ -9076,7 +9536,7 @@ The following contributors merged PRs in this release: - Button: Remove default border from the destructive button. ([53607](https://github.com/WordPress/gutenberg/pull/53607)) - LineHeightControl: Allow for more granular control of decimal places. ([52902](https://github.com/WordPress/gutenberg/pull/52902)) - Snackbar: Design and motion improvements. ([53248](https://github.com/WordPress/gutenberg/pull/53248)) -- Modal: +- Modal: - Add `headerActions` prop to enable buttons or other elements to be injected in the header. ([53328](https://github.com/WordPress/gutenberg/pull/53328)) - Enhance overlay interactions, enabling outside interactions without dismissal. ([52994](https://github.com/WordPress/gutenberg/pull/52994)) - ProgressBar: Update colors, including gray 300 for track color ([53349](https://github.com/WordPress/gutenberg/pull/53349)), theme system accent for indicator color ([53347](https://github.com/WordPress/gutenberg/pull/53347)), and the theme accent color variable. ([53632](https://github.com/WordPress/gutenberg/pull/53632)). @@ -9086,7 +9546,7 @@ The following contributors merged PRs in this release: - Add a `stretch` option to block's vertical alignment options. ([53325](https://github.com/WordPress/gutenberg/pull/53325)) - Exit upon pressing enter in an empty paragraph at the end of the block. ([53311](https://github.com/WordPress/gutenberg/pull/53311)) - Classic block: Increase dimensions of modal and allow toggling fullscreen. ([53449](https://github.com/WordPress/gutenberg/pull/53449)) -- Details block: +- Details block: - Add `accordion` and `toggle` keywords to improve block's discoverability. ([53501](https://github.com/WordPress/gutenberg/pull/53501)) - Add layout and block spacing options. ([53282](https://github.com/WordPress/gutenberg/pull/53282)) - File block: Add block spacing options. ([45107](https://github.com/WordPress/gutenberg/pull/45107)) @@ -9126,7 +9586,7 @@ The following contributors merged PRs in this release: ### Bug Fixes -#### Commands +#### Commands - Style tweaks to fix metrics for resting and no results view in command palette. ([53497](https://github.com/WordPress/gutenberg/pull/53497)) - Order template results in Site Editor, to fix some templates not displaying. ([53286](https://github.com/WordPress/gutenberg/pull/53286)) - Don't allow access to Styles-related pages via the command palette in the hybrid theme. ([53123](https://github.com/WordPress/gutenberg/pull/53123)) @@ -9134,12 +9594,12 @@ The following contributors merged PRs in this release: #### Block Library - Button block: Avoid losing user changes when the `ButtonEdit` component re-renders. ([53507](https://github.com/WordPress/gutenberg/pull/53507)) - Cover block: Fix flickering when inserted in templates and also fix `isDark` calculation bugs. ([53253](https://github.com/WordPress/gutenberg/pull/53253)) -- Footnotes block: +- Footnotes block: - Ensure autosave works and escapes quotes as expected. ([53664](https://github.com/WordPress/gutenberg/pull/53664)) - Fix accidental override. ([53663](https://github.com/WordPress/gutenberg/pull/53663)) - Fix recursion into updating attributes when attributes is not an object. ([53257](https://github.com/WordPress/gutenberg/pull/53257)) - Remove Footnotes when interactive formatting is disabled. ([53474](https://github.com/WordPress/gutenberg/pull/53474)) -- Image block: +- Image block: - Fix image stretching with only height. ([53443](https://github.com/WordPress/gutenberg/pull/53443)) - Don't render `DimensionsTool` if it is not resizable. ([53181](https://github.com/WordPress/gutenberg/pull/53181)) - Fix stretched images constrained by max-width. ([53274](https://github.com/WordPress/gutenberg/pull/53274)) @@ -9255,7 +9715,7 @@ The following contributors merged PRs in this release: - Fix outdated specification in the "Anatomy of a Block". ([53581](https://github.com/WordPress/gutenberg/pull/53581)) #### How To Guides -- Block Tutorial: +- Block Tutorial: - Adds import from `@wordpress/i18n` in code example. ([53504](https://github.com/WordPress/gutenberg/pull/53504)) - Adds package.json configuration instructions to tutorial. ([53689](https://github.com/WordPress/gutenberg/pull/53689)) - Building a Custom Block Editor: Consolidate and update the guide. ([53159](https://github.com/WordPress/gutenberg/pull/53159)) @@ -9271,7 +9731,7 @@ The following contributors merged PRs in this release: - Edit Post: Update "PluginDocumentSettingPanel" documentation. ([53393](https://github.com/WordPress/gutenberg/pull/53393)) - Interface: Fix "complimentary" typo with "complementary". ([53413](https://github.com/WordPress/gutenberg/pull/53413)) -#### Interactivity API +#### Interactivity API - Revamp of README to include API Reference among other indications. ([53385](https://github.com/WordPress/gutenberg/pull/53385)) - Add missing section in ToC and minor improvements to Getting Started Guide. ([53362](https://github.com/WordPress/gutenberg/pull/53362)) - Update Interactivity API package README with clear references to documentation. ([53388](https://github.com/WordPress/gutenberg/pull/53388)) @@ -9293,7 +9753,7 @@ The following contributors merged PRs in this release: #### Block Library - Button block: Replace the `isSmall` deprecated prop in the `WidthPanel`. ([53472](https://github.com/WordPress/gutenberg/pull/53472)) - Details block: Remove unnecessary comment attributes. ([51610](https://github.com/WordPress/gutenberg/pull/51610)) -- Footnotes block: +- Footnotes block: - Add some test coverage for footnotes logic in useEntityBlockEditor. ([53376](https://github.com/WordPress/gutenberg/pull/53376)) - Checking type before using count(). ([53660](https://github.com/WordPress/gutenberg/pull/53660)) - LinkControl/LinkUI: Remove unused `className` prop. ([53348](https://github.com/WordPress/gutenberg/pull/53348)) @@ -9661,7 +10121,7 @@ The following contributors merged PRs in this release: ### Tools - Add GH action to enforce PR labels. ([52760](https://github.com/WordPress/gutenberg/pull/52760)) -- Changelog automation: +- Changelog automation: - Make Accessibility a top-level section. ([52900](https://github.com/WordPress/gutenberg/pull/52900)) - Update to work with consolidated a11y labels. ([52896](https://github.com/WordPress/gutenberg/pull/52896)) - Use the correct label to filter Mobile app PRs. ([53024](https://github.com/WordPress/gutenberg/pull/53024)) @@ -9960,7 +10420,7 @@ The following contributors merged PRs in this release: #### Project Management - Update issue gardening automation with new label. ([52173](https://github.com/WordPress/gutenberg/pull/52173)) - Revert "Update Changelog for 16.1.2". ([52433](https://github.com/WordPress/gutenberg/pull/52433)) -- Github workflow: Add a PHP backport changes action. ([52096](https://github.com/WordPress/gutenberg/pull/52096)) +- GitHub workflow: Add a PHP backport changes action. ([52096](https://github.com/WordPress/gutenberg/pull/52096)) ## First time contributors @@ -9999,7 +10459,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@getdave @mcsf +@getdave @mcsf = 16.2.0 = @@ -10063,13 +10523,13 @@ The following contributors merged PRs in this release: - Footnotes: save numbering through the entity provider (https://github.com/WordPress/gutenberg/pull/52423) #### Code Quality / Performance -- Iframe: avoid asset parsing & fix script localisation +- Iframe: avoid asset parsing & fix script localisation ## Contributors The following contributors merged PRs in this release: -@ellatrix @ockham @t-hamano +@ellatrix @ockham @t-hamano = 16.2.0-rc.1 = @@ -10322,7 +10782,7 @@ The following contributors merged PRs in this release: - Update delete page button label. ([51812](https://github.com/WordPress/gutenberg/pull/51812)) - Update versions in WP for 6.3. ([51984](https://github.com/WordPress/gutenberg/pull/51984)) - Wrap "Move to trash" and "Switch to draft" buttons when labels are too long to fit on a single row. ([52249](https://github.com/WordPress/gutenberg/pull/52249)) -- [Github-Actions-Workflows][Plugin-Release] Allow shipping a point-release for an older stable release. ([49082](https://github.com/WordPress/gutenberg/pull/49082)) +- [GitHub-Actions-Workflows][Plugin-Release] Allow shipping a point-release for an older stable release. ([49082](https://github.com/WordPress/gutenberg/pull/49082)) #### Block Library - Block Editor: Unify texts for Create pattern modal. ([52151](https://github.com/WordPress/gutenberg/pull/52151)) @@ -10637,7 +11097,7 @@ The following contributors merged PRs in this release: - Update delete page button label. ([51812](https://github.com/WordPress/gutenberg/pull/51812)) - Update versions in WP for 6.3. ([51984](https://github.com/WordPress/gutenberg/pull/51984)) - Wrap "Move to trash" and "Switch to draft" buttons when labels are too long to fit on a single row. ([52249](https://github.com/WordPress/gutenberg/pull/52249)) -- [Github-Actions-Workflows][Plugin-Release] Allow shipping a point-release for an older stable release. ([49082](https://github.com/WordPress/gutenberg/pull/49082)) +- [GitHub-Actions-Workflows][Plugin-Release] Allow shipping a point-release for an older stable release. ([49082](https://github.com/WordPress/gutenberg/pull/49082)) #### Block Library - Block Editor: Unify texts for Create pattern modal. ([52151](https://github.com/WordPress/gutenberg/pull/52151)) @@ -10963,7 +11423,7 @@ The following contributors merged PRs in this release: #### Block Variations - [Block Library - Post Terms]: Custom taxonomies do not show icons when transforming from the toolbar. ([51476](https://github.com/WordPress/gutenberg/pull/51476)) -#### Page Content Focus +#### Page Content Focus - Switch to Page panel when deselecting a block [51881](https://github.com/WordPress/gutenberg/pull/51881) - Don't show 'Back to page' notification when navigating away from page [51880](https://github.com/WordPress/gutenberg/pull/51880) - useBlockSync(): Reset inner blocks when component unmounts [51783](https://github.com/WordPress/gutenberg/pull/51783) @@ -11103,7 +11563,7 @@ The following contributors merged PRs in this release: - Babel config: Enable useSpread option for JSX transform to reduce transpilation. ([51574](https://github.com/WordPress/gutenberg/pull/51574)) - Lodash: Remove from lint staged type check. ([51698](https://github.com/WordPress/gutenberg/pull/51698)) - Performance Tests: Update the base point to compare against. ([51689](https://github.com/WordPress/gutenberg/pull/51689)) -- wp-env: Try to fix failing PHP Github actions. ([51513](https://github.com/WordPress/gutenberg/pull/51513)) +- wp-env: Try to fix failing PHP GitHub actions. ([51513](https://github.com/WordPress/gutenberg/pull/51513)) - npm lockfile: Hoist reakit and date-fns packages to the top. ([51500](https://github.com/WordPress/gutenberg/pull/51500)) #### Plugin @@ -11122,7 +11582,7 @@ The following PRs were merged by first time contributors: The following contributors merged PRs in this release: -@aaronrobertshaw @afercia @alexstine @andrewserong @aristath @artemiomorales @aurooba @bangank36 @c4rl0sbr4v0 @carolinan @ciampo @dcalhoun @derekblank @diegohaz @draganescu @ellatrix @fabiankaegy @fluiddot @geriux @getdave @glendaviesnz @jameskoster @jasmussen @jeryj @jhnstn @jsnajdr @juanfra @kozer @luisherranz @MaggieCabrera @Mamaduka @matiasbenedetto @mcliwanow @mcsf @mikachan @n2erjo00 @noahtallen @noisysocks @ntsekouras @oandregal @okmttdhr @paulopmt1 @pbking @peterwilsoncc @pooja-muchandikar @ramonjd @richtabor @samnajian @SantosGuillamot @SavPhill @SaxonF @scruffian @shimotmk @Sidsector9 @SiobhyB @spacedmonkey @stokesman @sunyatasattva @t-hamano @talldan @tellthemachines @tyxla @walbo @WunderBart @xerpa43 @youknowriad @priethor @ajlende @mirka +@aaronrobertshaw @afercia @alexstine @andrewserong @aristath @artemiomorales @aurooba @bangank36 @c4rl0sbr4v0 @carolinan @ciampo @dcalhoun @derekblank @diegohaz @draganescu @ellatrix @fabiankaegy @fluiddot @geriux @getdave @glendaviesnz @jameskoster @jasmussen @jeryj @jhnstn @jsnajdr @juanfra @kozer @luisherranz @MaggieCabrera @Mamaduka @matiasbenedetto @mcliwanow @mcsf @mikachan @n2erjo00 @noahtallen @noisysocks @ntsekouras @oandregal @okmttdhr @paulopmt1 @pbking @peterwilsoncc @pooja-muchandikar @ramonjd @richtabor @samnajian @SantosGuillamot @SavPhill @SaxonF @scruffian @shimotmk @Sidsector9 @SiobhyB @spacedmonkey @stokesman @sunyatasattva @t-hamano @talldan @tellthemachines @tyxla @walbo @WunderBart @xerpa43 @youknowriad @priethor @ajlende @mirka @@ -11380,7 +11840,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@hellofromtonya @ndiego +@hellofromtonya @ndiego = 15.9.0 = @@ -11440,7 +11900,7 @@ The following contributors merged PRs in this release: - Remove `unwrap` from transforms and add `ungroup` to more blocks. ([50385](https://github.com/WordPress/gutenberg/pull/50385)) - Add new API to allow inserter items to be prioritised. ([50510](https://github.com/WordPress/gutenberg/pull/50510)) - Integrate `prioritizedInserterBlocks` API to slash inserter. ([50658](https://github.com/WordPress/gutenberg/pull/50658)) - + #### Global Styles - Custom CSS: Force display of in custom css input boxes to LTR. ([50768](https://github.com/WordPress/gutenberg/pull/50768)) - Styles Navigation Screen: Add Style Book. ([50566](https://github.com/WordPress/gutenberg/pull/50566)) @@ -11459,7 +11919,7 @@ The following contributors merged PRs in this release: #### Accessibility - Modals: Update the Cancel action's button design. ([50544](https://github.com/WordPress/gutenberg/pull/50544)) - Writing flow: Improve keyboard navigation on certain input types. ([43667](https://github.com/WordPress/gutenberg/pull/43667)) - + #### Icons - Add new `HeadingLevel` icons. ([50856](https://github.com/WordPress/gutenberg/pull/50856)) - Smaller external link icon. ([50728](https://github.com/WordPress/gutenberg/pull/50728)) @@ -12171,7 +12631,7 @@ Continued the work refactor away from Lodash usages to reduce the build size - Re-write of the landing page. ([49643](https://github.com/WordPress/gutenberg/pull/49643)) - Improve insertBlock(s) documentation. ([50078](https://github.com/WordPress/gutenberg/pull/50078)) - Small Typo: Remove dots. ([49853](https://github.com/WordPress/gutenberg/pull/49853)) - + ### Code Quality - Fix PrivateInserter import. ([50038](https://github.com/WordPress/gutenberg/pull/50038)) @@ -12189,7 +12649,7 @@ Continued the work refactor away from Lodash usages to reduce the build size - Migrate CPT end-to-end tests to Playwright. ([50031](https://github.com/WordPress/gutenberg/pull/50031)) - Fonts API: Add tests for gutenberg_add_registered_fonts_to_theme_json(). ([50049](https://github.com/WordPress/gutenberg/pull/50049)) - Expand multi-line block tests. ([49732](https://github.com/WordPress/gutenberg/pull/49732)) -- Rich text test helpers mimic user events. ([49804](https://github.com/WordPress/gutenberg/pull/49804)) +- Rich text test helpers mimic user events. ([49804](https://github.com/WordPress/gutenberg/pull/49804)) - Fix editor canvas detaching error in end-to-end tests. ([49374](https://github.com/WordPress/gutenberg/pull/49374)) #### Build Tooling @@ -12359,7 +12819,7 @@ The following contributors merged PRs in this release: ### Performance -- Continued the work refactor away from Lodash usages to reduce the build size +- Continued the work refactor away from Lodash usages to reduce the build size ([49725](https://github.com/WordPress/gutenberg/pull/49725), [49724](https://github.com/WordPress/gutenberg/pull/49724), [49638](https://github.com/WordPress/gutenberg/pull/49638), [49654](https://github.com/WordPress/gutenberg/pull/49654), [49639](https://github.com/WordPress/gutenberg/pull/49639), [49637](https://github.com/WordPress/gutenberg/pull/49637), [49727](https://github.com/WordPress/gutenberg/pull/49727)) ### Documentation @@ -13055,7 +13515,7 @@ The following contributors merged PRs in this release: - Create automatic change higher order reducer. ([48312](https://github.com/WordPress/gutenberg/pull/48312)) - [Inserter]: Preload media categories empty check - client side. ([47503](https://github.com/WordPress/gutenberg/pull/47503)) -#### Post Editor +#### Post Editor - Revert iframed editor for WP core only. ([48076](https://github.com/WordPress/gutenberg/pull/48076)) - PageAttributesCheck: Return boolean value directly from the selector. ([48336](https://github.com/WordPress/gutenberg/pull/48336)) - Apply busy status to the publish button in progress and unify button width. ([48444](https://github.com/WordPress/gutenberg/pull/48444)) @@ -13173,7 +13633,7 @@ The following contributors merged PRs in this release: ### Performance -#### Block Editor +#### Block Editor - Block Editor: Improve empty `getBlockParents()` perf. ([48242](https://github.com/WordPress/gutenberg/pull/48242)) - Fix perf regression in duotone hooks. ([48401](https://github.com/WordPress/gutenberg/pull/48401)) - Writing flow: Avoid recalc style on every selection change. ([48409](https://github.com/WordPress/gutenberg/pull/48409)) @@ -13204,7 +13664,7 @@ The following contributors merged PRs in this release: - Lodash: Remove some `_.get()` from Image block. ([48489](https://github.com/WordPress/gutenberg/pull/48489)) - Playwright: Fix request utils for non Docker envs. ([48206](https://github.com/WordPress/gutenberg/pull/48206)) - [Private APIs] Only prevent module re-registration if IS_WORDPRESS_CORE. ([48352](https://github.com/WordPress/gutenberg/pull/48352)) -- Add a manual performance job that we can trigger from Github UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) +- Add a manual performance job that we can trigger from GitHub UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) - Track new front-end metric: LCP-TTFB. ([48288](https://github.com/WordPress/gutenberg/pull/48288)) ### Documentation @@ -13218,7 +13678,7 @@ The following contributors merged PRs in this release: - Updates to the curating the editor experience to include 6.1 & 6.2 items. ([48294](https://github.com/WordPress/gutenberg/pull/48294)) ### Tools -#### Testing +#### Testing - Migrate `switch-to-draft` to Playwright. ([48120](https://github.com/WordPress/gutenberg/pull/48120)) - VizReg end-to-end tests: Programmatically test all combinations of a given list of props/values. ([48260](https://github.com/WordPress/gutenberg/pull/48260)) - Update end-to-end test snapshots to Jest 29 default. ([48626](https://github.com/WordPress/gutenberg/pull/48626)) @@ -13280,7 +13740,7 @@ The following contributors merged PRs in this release: - Create automatic change higher order reducer. ([48312](https://github.com/WordPress/gutenberg/pull/48312)) - [Inserter]: Preload media categories empty check - client side. ([47503](https://github.com/WordPress/gutenberg/pull/47503)) -#### Post Editor +#### Post Editor - Revert iframed editor for WP core only. ([48076](https://github.com/WordPress/gutenberg/pull/48076)) - PageAttributesCheck: Return boolean value directly from the selector. ([48336](https://github.com/WordPress/gutenberg/pull/48336)) - Apply busy status to the publish button in progress and unify button width. ([48444](https://github.com/WordPress/gutenberg/pull/48444)) @@ -13398,7 +13858,7 @@ The following contributors merged PRs in this release: ### Performance -#### Block Editor +#### Block Editor - Block Editor: Improve empty `getBlockParents()` perf. ([48242](https://github.com/WordPress/gutenberg/pull/48242)) - Fix perf regression in duotone hooks. ([48401](https://github.com/WordPress/gutenberg/pull/48401)) - Writing flow: Avoid recalc style on every selection change. ([48409](https://github.com/WordPress/gutenberg/pull/48409)) @@ -13429,7 +13889,7 @@ The following contributors merged PRs in this release: - Lodash: Remove some `_.get()` from Image block. ([48489](https://github.com/WordPress/gutenberg/pull/48489)) - Playwright: Fix request utils for non Docker envs. ([48206](https://github.com/WordPress/gutenberg/pull/48206)) - [Private APIs] Only prevent module re-registration if IS_WORDPRESS_CORE. ([48352](https://github.com/WordPress/gutenberg/pull/48352)) -- Add a manual performance job that we can trigger from Github UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) +- Add a manual performance job that we can trigger from GitHub UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) - Track new front-end metric: LCP-TTFB. ([48288](https://github.com/WordPress/gutenberg/pull/48288)) ### Documentation @@ -13443,7 +13903,7 @@ The following contributors merged PRs in this release: - Updates to the curating the editor experience to include 6.1 & 6.2 items. ([48294](https://github.com/WordPress/gutenberg/pull/48294)) ### Tools -#### Testing +#### Testing - Migrate `switch-to-draft` to Playwright. ([48120](https://github.com/WordPress/gutenberg/pull/48120)) - VizReg end-to-end tests: Programmatically test all combinations of a given list of props/values. ([48260](https://github.com/WordPress/gutenberg/pull/48260)) - Update end-to-end test snapshots to Jest 29 default. ([48626](https://github.com/WordPress/gutenberg/pull/48626)) @@ -13702,7 +14162,7 @@ The following contributors merged PRs in this release: - [Automated Testing]: Fix wrong button fixture. ([48305](https://github.com/WordPress/gutenberg/pull/48305)) #### Build Tooling -- Add a manual performance job that we can trigger from Github UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) +- Add a manual performance job that we can trigger from GitHub UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) - Add command to run performance tests in debug mode. ([48614](https://github.com/WordPress/gutenberg/pull/48614)) - Make the performance tests more stable. ([48094](https://github.com/WordPress/gutenberg/pull/48094)) - SpacingSizesControl: Fix white dot on thumb. ([48574](https://github.com/WordPress/gutenberg/pull/48574)) @@ -13797,7 +14257,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@jsnajdr +@jsnajdr = 15.2.2 = @@ -14882,7 +15342,7 @@ The following contributors merged PRs in this release: ### Bug Fixes #### Block Editor -- Move block variation picker styles in the iframe ([47109](https://github.com/WordPress/gutenberg/pull/47109)) +- Move block variation picker styles in the iframe ([47109](https://github.com/WordPress/gutenberg/pull/47109)) - Add useBlockPreview styles in iframe ([47110](https://github.com/WordPress/gutenberg/pull/47110)) #### Block Library @@ -15437,7 +15897,7 @@ Fixes compatibility with WordPress 6.0.x. This includes the following PRs: - #46809 Broadly, this needed to include the two refactors of the Theme_JSON compatibility files, along with two fixes switching `wp_*` and `gutenberg_*` function variants as needed. - + ## Contributors The following contributors were involved with this release: @@ -15744,7 +16204,7 @@ The following contributors merged PRs in this release: ### Tools #### Build Tooling -- Adds Github Action to validate Gradle Wrapper. ([46247](https://github.com/WordPress/gutenberg/pull/46247)) +- Adds GitHub Action to validate Gradle Wrapper. ([46247](https://github.com/WordPress/gutenberg/pull/46247)) - Prevent api-fetch and core-data from being imported in the block editor package. ([46302](https://github.com/WordPress/gutenberg/pull/46302)) - Serialize the map objects properly in the Redux dev tools. ([46282](https://github.com/WordPress/gutenberg/pull/46282)) @@ -16022,7 +16482,7 @@ The following contributors merged PRs in this release: ### Tools #### Build Tooling -- Adds Github Action to validate Gradle Wrapper. ([46247](https://github.com/WordPress/gutenberg/pull/46247)) +- Adds GitHub Action to validate Gradle Wrapper. ([46247](https://github.com/WordPress/gutenberg/pull/46247)) - Prevent api-fetch and core-data from being imported in the block editor package. ([46302](https://github.com/WordPress/gutenberg/pull/46302)) - Serialize the map objects properly in the Redux dev tools. ([46282](https://github.com/WordPress/gutenberg/pull/46282)) @@ -16059,7 +16519,7 @@ The following contributors merged PRs in this release: The following contributors merged PRs in this release: -@youknowriad +@youknowriad = 14.7.2 = @@ -17420,7 +17880,7 @@ The following contributors merged PRs in this release: - Query Loop: Hide instructions for FormTokenField. ([44641](https://github.com/WordPress/gutenberg/pull/44641)) - Tag Cloud: Remove `strtolower` around taxonomy name. ([16112](https://github.com/WordPress/gutenberg/pull/16112)) - Video: Update placeholder style. ([44215](https://github.com/WordPress/gutenberg/pull/44215)) - + #### Components - FontSizePicker: Make control take up full width. ([44559](https://github.com/WordPress/gutenberg/pull/44559)) - Placeholder: Remove unnecessary background color. ([44497](https://github.com/WordPress/gutenberg/pull/44497)) @@ -17853,7 +18313,7 @@ The following PRs were merged by first time contributors: The following contributors merged PRs in this release: -@aaronrobertshaw @ajlende @annezazu @apmatthews @aristath @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @dcalhoun @draganescu @ellatrix @geriux @glendaviesnz @gziolo @jasmussen @jorgefilipecosta @kebbet @kkoppenhaver @Mamaduka @matiasbenedetto @mcsf @michalczaplinski @mirka @mtias @noisysocks @ntsekouras @pagelab @ramonjd @t-hamano @talldan @tellthemachines @tyxla @walbo @youknowriad +@aaronrobertshaw @ajlende @annezazu @apmatthews @aristath @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @dcalhoun @draganescu @ellatrix @geriux @glendaviesnz @gziolo @jasmussen @jorgefilipecosta @kebbet @kkoppenhaver @Mamaduka @matiasbenedetto @mcsf @michalczaplinski @mirka @mtias @noisysocks @ntsekouras @pagelab @ramonjd @t-hamano @talldan @tellthemachines @tyxla @walbo @youknowriad @@ -20230,7 +20690,7 @@ The following contributors merged PRs in this release: - JS Error Tracking: Allow custom error reporting logic to be called in Error Boundaries via a WP action hook. ([42024](https://github.com/WordPress/gutenberg/pull/42024)) -### Accessibility +### Accessibility - Add aria-checked to the selected heading level menu item. ([42273](https://github.com/WordPress/gutenberg/pull/42273)) - Fix tabbing from first or last block in site editor. ([42036](https://github.com/WordPress/gutenberg/pull/42036)) @@ -20339,7 +20799,7 @@ The following contributors merged PRs in this release: - Tip: Covert component to TypeScript. ([42262](https://github.com/WordPress/gutenberg/pull/42262)) - VisuallyHidden: Convert component to TypeScript. ([42220](https://github.com/WordPress/gutenberg/pull/42220)) - Spacer: Complete TypeScript migration of component. ([42013](https://github.com/WordPress/gutenberg/pull/42013)) -- +- #### Components - Add eslint to prevent SSR breakage. ([42248](https://github.com/WordPress/gutenberg/pull/42248)) - Fix typos in components changelog. ([42244](https://github.com/WordPress/gutenberg/pull/42244)) @@ -20429,7 +20889,7 @@ The following contributors merged PRs in this release: ### Enhancements -#### UI +#### UI - Increase fade intensity during spotlight mode. ([40454](https://github.com/WordPress/gutenberg/pull/40454)) - Improvements to "inherit default layout" toggle. ([41893](https://github.com/WordPress/gutenberg/pull/41893)) - Make it easier to select "Edit visually" when in "Edit as HTML. ([41516](https://github.com/WordPress/gutenberg/pull/41516)) @@ -20489,7 +20949,7 @@ The following contributors merged PRs in this release: - [Block Editor]: Fix content loss from `replaceInnerBlocks` with controlled blocks. ([41948](https://github.com/WordPress/gutenberg/pull/41948)) - Fix action button spacing on the widget editor. ([41915](https://github.com/WordPress/gutenberg/pull/41915)) - Render duotone presets in pattern preview. ([41249](https://github.com/WordPress/gutenberg/pull/41249)) - + #### Block Library - List v2: - Fix impossible to outdent multiple list items. ([41713](https://github.com/WordPress/gutenberg/pull/41713)) @@ -21805,7 +22265,7 @@ The following contributors merged PRs in this release: ### Bug Fixes -- Fix the position of the block inserter in between blocks ([40919](https://github.com/WordPress/gutenberg/pull/40919)) +- Fix the position of the block inserter in between blocks ([40919](https://github.com/WordPress/gutenberg/pull/40919)) = 13.2.0 = @@ -22175,7 +22635,7 @@ The following PRs were merged by first time contributors: - Fixed focus loss when navigating the guide component. ([40324](https://github.com/WordPress/gutenberg/pull/40324)) - Navigation block: After choosing an option from Select Menu, focus after block rerender. ([40390](https://github.com/WordPress/gutenberg/pull/40390)) - Block Styles: Remove unnecessary button role and 'onKeyDown' handler. ([40427](https://github.com/WordPress/gutenberg/pull/40427)) - + ### Performance - Added a context param to sidebar entity request. ([40148](https://github.com/WordPress/gutenberg/pull/40148)) @@ -22213,12 +22673,12 @@ The following PRs were merged by first time contributors: - Stop exporting individual color objects from color values file. ([40387](https://github.com/WordPress/gutenberg/pull/40387)) - Added reusable BlockPopover and BlockPopoverInbetween components. ([40441](https://github.com/WordPress/gutenberg/pull/40441)) - Cleaned BlockMover component and styles. ([40379](https://github.com/WordPress/gutenberg/pull/40379)) - + #### Packages - Block Editor: Remove unused sub-components in list view. ([40448](https://github.com/WordPress/gutenberg/pull/40448)) - Data: Cancel render queue in a more straightforward way. ([40433](https://github.com/WordPress/gutenberg/pull/40433)) - + #### Plugin - Added `edit` context to patterns REST controllers. ([40259](https://github.com/WordPress/gutenberg/pull/40259)) @@ -39262,7 +39722,7 @@ Add knobs to the [ColorIndicator Story](https://github.com/WordPress/gutenberg/p * `is_gutenberg_page` incorrectly assumes `get_current_screen` exists, add check. * Brings code inline with CSS standards by switching font weight to numeric values. * Wrapped component would not the most up-to-date store values if it incurred a store state change during its own mount (e.g. dispatching during its own constructor), resolved by rerunning selection. -* Display an error message if Javascript is disabled. +* Display an error message if JavaScript is disabled. * Update to React 16.6.3. * Adds missing components dependency for RichText. * Refactors list block to remove previously exposed RichText/TinyMCE logic. diff --git a/docs/contributors/code/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md index 06f86715a65a06..d89df5876e3804 100644 --- a/docs/contributors/code/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -141,9 +141,9 @@ An **plugin-only API** is one which is planned for eventual public availability, Plugin-only APIs are excluded from WordPress Core and only available in the Gutenberg Plugin: ```js -// Using process.env.IS_GUTENBERG_PLUGIN allows Webpack to exclude this +// Using globalThis.IS_GUTENBERG_PLUGIN allows Webpack to exclude this // export from WordPress core: -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { export { doSomethingExciting } from './api'; } ``` @@ -448,8 +448,8 @@ lock( privateApis, { privateEverywhere, privateInCorePublicInPlugin } ); // The privateInCorePublicInPlugin function is explicitly exported, // but this export will not be merged into WordPress core thanks to -// the process.env.IS_GUTENBERG_PLUGIN check. -if ( process.env.IS_GUTENBERG_PLUGIN ) { +// the globalThis.IS_GUTENBERG_PLUGIN check. +if ( globalThis.IS_GUTENBERG_PLUGIN ) { export const privateInCorePublicInPlugin = unlock( privateApis ).privateInCorePublicInPlugin; } diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 4c8950eb5e7cd6..f304ec9cd3a487 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -320,13 +320,13 @@ If an RC already exists for a new version, you _need_ to cherry-pick the same co The cherry-picking process can be automated with the [`npm run cherry-pick`](/docs/contributors/code/auto-cherry-picking.md) script, but be sure to use the `Backport to Gutenberg Minor Release` label when running the script. -You must also ensure that all PRs being included are assigned to the Github Milestone on which the minor release is based. Bear in mind, that when PRs are _merged_ they are automatically assigned a milestone for the next _stable_ release. Therefore you will need to go back through each PR in Github and re-assign the Milestone. +You must also ensure that all PRs being included are assigned to the GitHub Milestone on which the minor release is based. Bear in mind, that when PRs are _merged_ they are automatically assigned a milestone for the next _stable_ release. Therefore you will need to go back through each PR in GitHub and re-assign the Milestone. For example, if you are releasing version `12.5.4`, then all PRs picked for that release must be unassigned from the `12.6` Milestone and instead assigned to the `12.5` Milestone. Once cherry picking is complete, you can also remove the `Backport to Gutenberg Minor Release` label from the PRs. -Once you have the stable release branch in order and the correct Milestone assigned to your PRs you can _push the branch to Github_ and continue with the release process using the Github website GUI. +Once you have the stable release branch in order and the correct Milestone assigned to your PRs you can _push the branch to GitHub_ and continue with the release process using the GitHub website GUI. #### Running the minor release diff --git a/docs/contributors/folder-structure.md b/docs/contributors/folder-structure.md index cbad182a58f447..e20ba81708a064 100644 --- a/docs/contributors/folder-structure.md +++ b/docs/contributors/folder-structure.md @@ -76,7 +76,7 @@ The following snippet explains how the Gutenberg repository is structured omitti │ Set of documentation pages composing the [Block editor handbook](https://developer.wordpress.org/block-editor/). │ ├── platform-docs - │ Documentation website targetted to non WordPress developpers + │ Documentation website targeted to non WordPress developers │ using Gutenberg in their own applications. │ Deployed on [https://wordpress.org/gutenberg-framework/](https://wordpress.org/gutenberg-framework/). │ diff --git a/docs/explanations/README.md b/docs/explanations/README.md index 04d9f8e9e15b52..2d0da6cabdf67c 100644 --- a/docs/explanations/README.md +++ b/docs/explanations/README.md @@ -8,5 +8,5 @@ - [Block Editor Performance](/docs/explanations/architecture/performance.md). - What are the decision decisions behind the Data Module? - [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/explanations/architecture/automated-testing.md) -- [What’s the difference between the different editor packages? What’s the purpose of each package?](/docs/explanations/architecture/modularity.md/#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) +- [What’s the difference between the different editor packages? What’s the purpose of each package?](/docs/explanations/architecture/modularity.md#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) - [Template and template parts flows](/docs/explanations/architecture/full-site-editing-templates.md) diff --git a/docs/explanations/architecture/performance.md b/docs/explanations/architecture/performance.md index de3b7b5dcdbd5b..4c8b6386b9263b 100644 --- a/docs/explanations/architecture/performance.md +++ b/docs/explanations/architecture/performance.md @@ -69,7 +69,7 @@ The performance results for each commit are pushed to codevitals and can be seen It's thus very important to ensure that the metric being computed is stable. Meaning, if you run the same test twice with the same code and environment, you'll get results that are close. -Our performance job runs Github CI which means that we can't trust the consistency of the numbers that we get between two similar job runs. Github CI may allocate different CPU and memory resources for us over time for instance. To alleviate this problem, each time we run the performance job on the trunk branch, we compare the current commit's performance to a fixed reference commit hash, which allows us to track the relative difference between the current commit and the reference commit consistently regardless of environment changes. +Our performance job runs GitHub CI which means that we can't trust the consistency of the numbers that we get between two similar job runs. GitHub CI may allocate different CPU and memory resources for us over time for instance. To alleviate this problem, each time we run the performance job on the trunk branch, we compare the current commit's performance to a fixed reference commit hash, which allows us to track the relative difference between the current commit and the reference commit consistently regardless of environment changes. ### Update the reference commit @@ -77,7 +77,7 @@ Gutenberg supports only two WP versions, this impacts the performance job in two - The base WP version used to run the performance job needs to be updated, when the minimum version supported by Gutenberg changes. In order to do that, we rely on the `Tested up to` flag of the plugin's `readme.txt` file. So each time that flag is changed, the version used for the performance job is changed as well. - - Updating the WP version used for performance jobs means that there's a high chance that the reference commit used for performance test stability becomes incompatible with the WP version that is used. So every time, the `Tested up to` flag is updated in the `readme.txt` is changed, we also have to update the reference commit that is used in `.github/workflows/performance.yml`. + - Updating the WP version used for performance jobs means that there's a high chance that the reference commit used for performance test stability becomes incompatible with the WP version that is used. So every time, the `Tested up to` flag is updated in the `readme.txt` is changed, we also have to update the reference commit that is used in `.github/workflows/performance.yml`. The new reference commit hash that is chosen needs to meet the following requirements: diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 72d7bfd760d657..8ac489e3c154a2 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -200,7 +200,7 @@ Our approach—as outlined in [the technical overview introduction](https://make This also [gives us the flexibility](https://github.com/WordPress/gutenberg/issues/1516) to store those blocks that are inherently separate from the content stream (reusable pieces like widgets or small post type elements) elsewhere, and just keep token references for their placement. -We suggest you look at the [Gutenberg key concepts](/docs/getting-started/architecture/key-concepts.md) to learn more about how this aspect of the project works. +We suggest you look at the [Gutenberg key concepts](/docs/explanations/architecture/key-concepts.md) to learn more about how this aspect of the project works. ### How can I parse the post content back out into blocks in PHP or JS? diff --git a/docs/getting-started/fundamentals/README.md b/docs/getting-started/fundamentals/README.md index fd2941711dd01b..42dbace9d7ba3f 100644 --- a/docs/getting-started/fundamentals/README.md +++ b/docs/getting-started/fundamentals/README.md @@ -9,4 +9,4 @@ This section provides an introduction to the most relevant concepts in block dev 1. **[The block in the Editor](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-in-the-editor):** How a block, as a React component, is loaded in the Block Editor and an overview of its structure. 1. **[Markup representation of a block](https://developer.wordpress.org/block-editor/getting-started/fundamentals/markup-representation-block):** How blocks are represented in the database, theme templates, and patterns. 1. **[Static or Dynamic rendering of a block](https://developer.wordpress.org/block-editor/getting-started/fundamentals/static-dynamic-rendering):** How blocks generate their front-end output either dynamically or statically. -1. **[Javascript in the Block Editor](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor):** How to work with modern Javascript when developing for the Block Editor. +1. **[JavaScript in the Block Editor](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor):** How to work with modern JavaScript when developing for the Block Editor. diff --git a/docs/getting-started/fundamentals/file-structure-of-a-block.md b/docs/getting-started/fundamentals/file-structure-of-a-block.md index eb3f38e0be5c4f..24972b219e0b60 100644 --- a/docs/getting-started/fundamentals/file-structure-of-a-block.md +++ b/docs/getting-started/fundamentals/file-structure-of-a-block.md @@ -29,7 +29,7 @@ The [build process](docs/block-editor/getting-started/fundamentals/javascript-in The `block.json` file contains the [block's metadata](docs/block-editor/reference-guides/block-api/block-metadata/), streamlining its definition and registration across client-side and server-side environments. -This file includes the block name, description, [attributes](docs/block-editor/reference-guides/block-api/block-attributes.md), [supports](docs/block-editor/reference-guides/block-api/block-supports.md), and more, as well as the locations of essential files responsible for the block's functionality, appearance, and styling. +This file includes the block name, description, [attributes](docs/block-editor/reference-guides/block-api/block-attributes/), [supports](docs/block-editor/reference-guides/block-api/block-supports/), and more, as well as the locations of essential files responsible for the block's functionality, appearance, and styling. When a build process is applied, the `block.json` file and the other generated files are moved to a designated folder, often the `build` folder. Consequently, the file paths specified within `block.json` point to these processed, bundled versions of the files. diff --git a/docs/getting-started/fundamentals/javascript-in-the-block-editor.md b/docs/getting-started/fundamentals/javascript-in-the-block-editor.md index 42d7363b6aa4df..348b95ba88da3c 100644 --- a/docs/getting-started/fundamentals/javascript-in-the-block-editor.md +++ b/docs/getting-started/fundamentals/javascript-in-the-block-editor.md @@ -1,20 +1,20 @@ -# Working with Javascript for the Block Editor +# Working with JavaScript for the Block Editor Developing blocks for the Block Editor often involves using modern JavaScript (ESNext and JSX), and most examples here in the Block Editor Handbook are written in these syntaxes. -However, this form of JavaScript must be transformed into a browser-compatible format, necessitating a build step. This process transforms, bundles, and optimizes JavaScript source code and related assets into a format suitable for production environments. +However, this form of JavaScript must be transformed into a browser-compatible format, necessitating a build step. This process transforms, bundles, and optimizes JavaScript source code and related assets into a format suitable for production environments. ## JavaScript with a build process Using a build process for block development unlocks the full potential of modern JavaScript, facilitating the use of ESNext and JSX. -[ESNext](https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview#standardization_process) refers to Javascript's most recent syntax and features. [JSX](https://react.dev/learn/writing-markup-with-jsx) is a syntax extension developed by the React project that enables you to write JavaScript that resembles HTML. +[ESNext](https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview#standardization_process) refers to JavaScript's most recent syntax and features. [JSX](https://react.dev/learn/writing-markup-with-jsx) is a syntax extension developed by the React project that enables you to write JavaScript that resembles HTML. Since browsers cannot directly execute ESNext and JSX, these syntaxes must be transformed into browser-compatible JavaScript. [webpack](https://webpack.js.org/concepts/why-webpack/) is a pluggable tool that processes and bundles JavaScript for browser compatibility. [Babel](https://babeljs.io/), a plugin for webpack, converts ESNext and JSX into standard JavaScript. -Configuring webpack and Babel can be challenging, so it's recommended that you use the [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) package. This tool simplifies development by preconfiguring both, so you rarely need to write custom webpack or Babel configurations. +Configuring webpack and Babel can be challenging, so it's recommended that you use the [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) package. This tool simplifies development by preconfiguring both, so you rarely need to write custom webpack or Babel configurations. For an introduction, refer to the [Get started with wp-scripts](/docs/getting-started/devenv/get-started-with-wp-scripts.md) guide. @@ -28,21 +28,21 @@ The diagram below provides an overview of the build process when using the `wp-s - **Development Mode (`npm run start`):** This mode is tailored for active development. It skips minification for easier debugging, generates source maps for better error tracking, and watches your source files for changes. When a change is detected, it automatically rebuilds the affected files, allowing you to see updates in real-time. -The `wp-scripts` package also facilitates the use of JavaScript modules, allowing code distribution across multiple files and resulting in a streamlined bundle after the build process. The [block-development-example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) GitHub repository provides some good examples. +The `wp-scripts` package also facilitates the use of JavaScript modules, allowing code distribution across multiple files and resulting in a streamlined bundle after the build process. The [block-development-example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) GitHub repository provides some good examples.
In most situations, no customization will be needed, but you can provide a webpack.config.js when using wp-scripts to modify the build process to suit your needs.
-## Javascript without a build process +## JavaScript without a build process Integrating JavaScript into your WordPress projects without a build process can be the most straightforward approach in specific scenarios. This is particularly true for projects that don't leverage JSX or other advanced JavaScript features requiring compilation. -When you opt out of a build process, you interact directly with WordPress's [Javascript APIs](/docs/reference-guides/packages/) through the global `wp` object. This means that all the methods and packages provided by WordPress are readily available, but with one caveat: you must manually manage script dependencies. This is done by adding [the handle](/docs/contributors/code/scripts.md) of each corresponding package to the dependency array of your enqueued JavaScript file. +When you opt out of a build process, you interact directly with WordPress's [JavaScript APIs](/docs/reference-guides/packages/) through the global `wp` object. This means that all the methods and packages provided by WordPress are readily available, but with one caveat: you must manually manage script dependencies. This is done by adding [the handle](/docs/contributors/code/scripts.md) of each corresponding package to the dependency array of your enqueued JavaScript file. For example, suppose you're creating a script that registers a new block [variation](/docs/reference-guides/block-api/block-variations.md) using the `registerBlockVariation` function from the [`blocks`](/docs/reference-guides/packages/packages-blocks.md) package. You must include `wp-blocks` in your script's dependency array. This guarantees that the `wp.blocks.registerBlockVariation` method is available and defined by the time your script executes. -In the following example, the `wp-blocks` dependency is defined when enqueuing the `variations.js` file. +In the following example, the `wp-blocks` dependency is defined when enqueuing the `variations.js` file. ```php function example_enqueue_block_variations() { @@ -84,10 +84,10 @@ Refer to [Enqueueing assets in the Editor](/docs/how-to-guides/enqueueing-assets ## Additional resources - [Package reference](/docs/reference-guides/packages.md) -- [Get started with wp-scripts](/docs/getting-started/devenv/get-started-with-wp-scripts.md) -- [Enqueueing assets in the Editor](/docs/how-to-guides/enqueueing-assets-in-the-editor.md) -- [WordPress package handles](/docs/contributors/code/scripts.md) -- [Javascript reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript) | MDN Web Docs +- [Get started with wp-scripts](/docs/getting-started/devenv/get-started-with-wp-scripts.md) +- [Enqueueing assets in the Editor](/docs/how-to-guides/enqueueing-assets-in-the-editor.md) +- [WordPress package handles](/docs/contributors/code/scripts.md) +- [JavaScript reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript) | MDN Web Docs - [block-development-examples](https://github.com/WordPress/block-development-examples) | GitHub repository - [block-theme-examples](https://github.com/wptrainingteam/block-theme-examples) | GitHub repository - [How webpack and WordPress packages interact](https://developer.wordpress.org/news/2023/04/how-webpack-and-wordpress-packages-interact/) | Developer Blog diff --git a/docs/getting-started/tutorial.md b/docs/getting-started/tutorial.md index 1f33eeb146e6c4..641ecad07ab9bc 100644 --- a/docs/getting-started/tutorial.md +++ b/docs/getting-started/tutorial.md @@ -156,7 +156,7 @@ Save the file and select the block in the Editor. You will now see both Color an #### Removing unnecessary code -For simplicity, the styling for the Copyright Date Block will be controlled entirely by the color and typography block supports. This block also does not have any front-end Javascript. Therefore, you don't need to specify stylesheets or a `viewScript` in the `block.json` file. +For simplicity, the styling for the Copyright Date Block will be controlled entirely by the color and typography block supports. This block also does not have any front-end JavaScript. Therefore, you don't need to specify stylesheets or a `viewScript` in the `block.json` file. 1. Remove the line for `editorStyle` 2. Remove the line for `style` @@ -781,7 +781,7 @@ This block validation error occurs because the `save()` function returns block c You will see more of these errors as you update the `save()` function in subsequent steps. Just click "Attempt Block Recovery" and update the page. -After preforming block recovery, open the Code editor and you will see the markup now looks like this. +After performing block recovery, open the Code editor and you will see the markup now looks like this. ```html diff --git a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md index d3628d991f872f..bf48c5db4e9bb9 100644 --- a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md +++ b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md @@ -178,7 +178,7 @@ As of Gutenberg version 14.2, the following controls are available: - `sticky` - Shows a dropdown to select how to handle sticky posts. - `taxQuery` - Shows available taxonomies filters for the currently selected post type. - `author` - Shows an input field to filter the query by author. -- `search` - Shows an input filed to filter the query by keywords. +- `search` - Shows an input field to filter the query by keywords. In our case, the property would look like this: diff --git a/docs/how-to-guides/curating-the-editor-experience/filters-and-hooks.md b/docs/how-to-guides/curating-the-editor-experience/filters-and-hooks.md index f5e21340adeaa0..b28fdf7d9e5db1 100644 --- a/docs/how-to-guides/curating-the-editor-experience/filters-and-hooks.md +++ b/docs/how-to-guides/curating-the-editor-experience/filters-and-hooks.md @@ -136,6 +136,43 @@ addFilter( ); ``` +## Block Filters + +Beyond curating the Editor itself, there are many ways that you can modify individual blocks. Perhaps you want to disable particular block supports like background color or define which settings should be displayed by default on specific blocks. + +One of the most commonly used filters is [`block_type_metadata`](https://developer.wordpress.org/reference/hooks/block_type_metadata/). It allows you to filter the raw metadata loaded from a block's `block.json` file when a block type is registered on the server with PHP. + +The filter takes one parameter: + +- `$metadata` (`array`) – metadata loaded from `block.json` for registering a block type. + +The `$metadata` array contains everything you might want to know about a block, from its description and [attributes](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/) to block [supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/). + +In the following example, background color and gradient support are disabled for Heading blocks. + +```php +function example_disable_heading_background_color_and_gradients( $metadata ) { + + // Only apply the filter to Heading blocks. + if ( ! isset( $metadata['name'] ) || 'core/heading' !== $metadata['name'] ) { + return $metadata; + } + + // Check if 'supports' key exists. + if ( isset( $metadata['supports'] ) && isset( $metadata['supports']['color'] ) ) { + + // Remove Background color and Gradients support. + $metadata['supports']['color']['background'] = false; + $metadata['supports']['color']['gradients'] = false; + } + + return $metadata; +} +add_filter( 'block_type_metadata', 'example_disable_heading_background_color_and_gradients' ); +``` + +You can learn more about the available block filters in the [Block Filters](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/) documentation. + ## Additional resources - [How to modify theme.json data using server-side filters](https://developer.wordpress.org/news/2023/07/05/how-to-modify-theme-json-data-using-server-side-filters/) (WordPress Developer Blog) diff --git a/docs/how-to-guides/data-basics/3-building-an-edit-form.md b/docs/how-to-guides/data-basics/3-building-an-edit-form.md index 34136ff4080800..65c4d0a5486a0d 100644 --- a/docs/how-to-guides/data-basics/3-building-an-edit-form.md +++ b/docs/how-to-guides/data-basics/3-building-an-edit-form.md @@ -215,7 +215,7 @@ As you can see, the `title` of an Entity Record is an object, but the `title` of This is no accident. Fields like `title`, `excerpt`, and `content` may contain [shortcodes](https://developer.wordpress.org/apis/handbook/shortcode/) or [dynamic blocks](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md), which means they can only be rendered on the server. For such fields, the REST API exposes both the `raw` markup _and_ the `rendered` string. For example, in the block editor, `content.rendered` could used as a visual preview, and `content.raw` could be used to populate the code editor. -So why is the `content` of an Edited Entity Record a string? Since Javascript is not be able to properly render arbitrary block markup, it stores only the `raw` markup without the `rendered` part. And since that's a string, the entire field becomes a string. +So why is the `content` of an Edited Entity Record a string? Since JavaScript is not be able to properly render arbitrary block markup, it stores only the `raw` markup without the `rendered` part. And since that's a string, the entire field becomes a string. We can now update `EditPageForm` accordingly. We can access the actions using the [`useDispatch`](/packages/data/README.md#usedispatch) hook similarly to how we use `useSelect` to access selectors: diff --git a/docs/how-to-guides/enqueueing-assets-in-the-editor.md b/docs/how-to-guides/enqueueing-assets-in-the-editor.md index 02d9020d351e38..fb0efb88e67967 100644 --- a/docs/how-to-guides/enqueueing-assets-in-the-editor.md +++ b/docs/how-to-guides/enqueueing-assets-in-the-editor.md @@ -18,7 +18,7 @@ There are different hooks to use depending on the answers to these questions, an Whenever you need to enqueue assets for the Editor itself (i.e. not the user-generated content), you should use the [`enqueue_block_editor_assets`](https://developer.wordpress.org/reference/hooks/enqueue_block_editor_assets/) hook coupled with the standard [`wp_enqueue_script`](https://developer.wordpress.org/reference/functions/wp_enqueue_script/) and [`wp_enqueue_style`](https://developer.wordpress.org/reference/functions/wp_enqueue_style/) functions. -Examples might be adding custom inspector or toolbar controls, registering block styles and variations in Javascript, registering Editor plugins, etc. +Examples might be adding custom inspector or toolbar controls, registering block styles and variations in JavaScript, registering Editor plugins, etc. ```php /** @@ -31,7 +31,7 @@ function example_enqueue_editor_assets() { ); wp_enqueue_style( 'example-editor-styles', - plugins_url( 'editor-styles.css', __FILE__ ) + plugins_url( 'editor-styles.css', __FILE__ ) ); } add_action( 'enqueue_block_editor_assets', 'example_enqueue_editor_assets' ); diff --git a/docs/how-to-guides/feature-flags.md b/docs/how-to-guides/feature-flags.md index 5855f93f8ed9fc..11c5ae881337dd 100644 --- a/docs/how-to-guides/feature-flags.md +++ b/docs/how-to-guides/feature-flags.md @@ -2,9 +2,9 @@ 'Feature flags' are variables that allow you to prevent specific code in the Gutenberg project from being shipped to WordPress core, and to run certain experimental features only in the plugin. -## Introducing `process.env.IS_GUTENBERG_PLUGIN` +## Introducing `globalThis.IS_GUTENBERG_PLUGIN` -The `process.env.IS_GUTENBERG_PLUGIN` is an environment variable whose value 'flags' whether code is running within the Gutenberg plugin. +The `globalThis.IS_GUTENBERG_PLUGIN` is an environment variable whose value 'flags' whether code is running within the Gutenberg plugin. When the codebase is built for the plugin, this variable will be set to `true`. When building for WordPress core, it will be set to `false` or `undefined`. @@ -19,8 +19,9 @@ function myPluginOnlyFeature() { // implementation } -export const pluginOnlyFeature = - process.env.IS_GUTENBERG_PLUGIN ? myPluginOnlyFeature : undefined; +export const pluginOnlyFeature = globalThis.IS_GUTENBERG_PLUGIN + ? myPluginOnlyFeature + : undefined; ``` In the above example, the `pluginOnlyFeature` export will be `undefined` in non-plugin environments such as WordPress core. @@ -32,37 +33,39 @@ If you're attempting to import and call a plugin-only feature, be sure to wrap t ```js import { pluginOnlyFeature } from '@wordpress/foo'; -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { pluginOnlyFeature(); } ``` ## How it works -During the webpack build, instances of `process.env.IS_GUTENBERG_PLUGIN` will be replaced using webpack's [define plugin](https://webpack.js.org/plugins/define-plugin/). +During the webpack build, instances of `globalThis.IS_GUTENBERG_PLUGIN` will be replaced using webpack's [define plugin](https://webpack.js.org/plugins/define-plugin/). For example, in the following code – ```js -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { pluginOnlyFeature(); } ``` -– the variable `process.env.IS_GUTENBERG_PLUGIN` will be replaced with the boolean `true` during the plugin-only build: +– the variable `globalThis.IS_GUTENBERG_PLUGIN` will be replaced with the boolean `true` during the plugin-only build: ```js -if ( true ) { // Wepack has replaced `process.env.IS_GUTENBERG_PLUGIN` with `true` +if ( true ) { + // Wepack has replaced `globalThis.IS_GUTENBERG_PLUGIN` with `true` pluginOnlyFeature(); } ``` This ensures that code within the body of the `if` statement will always be executed. -In WordPress core, the `process.env.IS_GUTENBERG_PLUGIN` variable is replaced with `undefined`. The built code looks like this: +In WordPress core, the `globalThis.IS_GUTENBERG_PLUGIN` variable is replaced with `undefined`. The built code looks like this: ```js -if ( undefined ) { // Wepack has replaced `process.env.IS_GUTENBERG_PLUGIN` with `undefined` +if ( undefined ) { + // Webpack has replaced `globalThis.IS_GUTENBERG_PLUGIN` with `undefined` pluginOnlyFeature(); } ``` @@ -99,6 +102,6 @@ In this case, the minification process will remove the entire `if` statement inc ## Frequently asked questions -### Why shouldn't I assign the result of an expression involving `IS_GUTENBERG_PLUGIN` to a variable, e.g. `const isMyFeatureActive = process.env.IS_GUTENBERG_PLUGIN === 2`? +### Why shouldn't I assign the result of an expression involving `IS_GUTENBERG_PLUGIN` to a variable, e.g. `const isMyFeatureActive = ! Object.is( undefined, globalThis.IS_GUTENBERG_PLUGIN )`? Introducing complexity may prevent webpack's minifier from identifying and therefore eliminating dead code. Therefore it is recommended to use the examples in this document to ensure your feature flag functions as intended. For further details, see the [Dead Code Elimination](#dead-code-elimination) section. 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 69f0606c936490..f71bd67bfaf2ec 100644 --- a/docs/how-to-guides/themes/global-settings-and-styles.md +++ b/docs/how-to-guides/themes/global-settings-and-styles.md @@ -310,20 +310,21 @@ There's one special setting property, `appearanceTools`, which is a boolean and To retain backward compatibility, the existing `add_theme_support` declarations that configure the block editor are retrofit in the proper categories for the top-level section. For example, if a theme uses `add_theme_support('disable-custom-colors')`, it'll be the same as setting `settings.color.custom` to `false`. If the `theme.json` contains any settings, these will take precedence over the values declared via `add_theme_support`. This is the complete list of equivalences: -| add_theme_support | theme.json setting | -| --------------------------- | --------------------------------------------------------- | -| `custom-line-height` | Set `typography.lineHeight` to `true`. | -| `custom-spacing` | Set `spacing.padding` to `true`. | -| `custom-units` | Provide the list of units via `spacing.units`. | -| `disable-custom-colors` | Set `color.custom` to `false`. | -| `disable-custom-font-sizes` | Set `typography.customFontSize` to `false`. | -| `disable-custom-gradients` | Set `color.customGradient` to `false`. | -| `editor-color-palette` | Provide the list of colors via `color.palette`. | -| `editor-font-sizes` | Provide the list of font size via `typography.fontSizes`. | -| `editor-gradient-presets` | Provide the list of gradients via `color.gradients`. | -| `appearance-tools` | Set `appearanceTools` to `true`. | -| `border` | Set `border: color, radius, style, width` to `true`. | -| `link-color ` | Set `color.link` to `true`. | +| add_theme_support | theme.json setting | +| --------------------------- | ------------------------------------------------------------- | +| `custom-line-height` | Set `typography.lineHeight` to `true`. | +| `custom-spacing` | Set `spacing.padding` to `true`. | +| `custom-units` | Provide the list of units via `spacing.units`. | +| `disable-custom-colors` | Set `color.custom` to `false`. | +| `disable-custom-font-sizes` | Set `typography.customFontSize` to `false`. | +| `disable-custom-gradients` | Set `color.customGradient` to `false`. | +| `editor-color-palette` | Provide the list of colors via `color.palette`. | +| `editor-font-sizes` | Provide the list of font size via `typography.fontSizes`. | +| `editor-gradient-presets` | Provide the list of gradients via `color.gradients`. | +| `editor-spacing-sizes` | Provide the list of spacing sizes via `spacing.spacingSizes`. | +| `appearance-tools` | Set `appearanceTools` to `true`. | +| `border` | Set `border: color, radius, style, width` to `true`. | +| `link-color ` | Set `color.link` to `true`. | #### Presets @@ -336,16 +337,7 @@ The following presets can be defined via `theme.json`: - `color.palette`: - generates 3 classes per preset value: color, background-color, and border-color. - generates a single custom property per preset value. -- `spacing.spacingScale`: used to generate an array of spacing preset sizes for use with padding, margin, and gap settings. - - `operator`: specifies how to calculate the steps with either `*` for multiplier, or `+` for sum. - - `increment`: the amount to increment each step by. Core by default uses a 'perfect 5th' multiplier of `1.5`. - - `steps`: the number of steps to generate in the spacing scale. The default is 7. To prevent the generation of the spacing presets, and to disable the related UI, this can be set to `0`. - - `mediumStep`: the steps in the scale are generated descending and ascending from a medium step, so this should be the size value of the medium space, without the unit. The default medium step is `1.5rem` so the mediumStep value is `1.5`. - - `unit`: the unit the scale uses, eg. `px, rem, em, %`. The default is `rem`. -- `spacing.spacingSizes`: themes can choose to include a static `spacing.spacingSizes` array of spacing preset sizes if they have a sequence of sizes that can't be generated via an increment or multiplier. - - `name`: a human readable name for the size, eg. `Small, Medium, Large`. - - `slug`: the machine readable name. In order to provide the best cross site/theme compatibility the slugs should be in the format, "10","20","30","40","50","60", with "50" representing the `Medium` size value. - - `size`: the size, including the unit, eg. `1.5rem`. It is possible to include fluid values like `clamp(2rem, 10vw, 20rem)`. +- `spacing.spacingSizes`/`spacing.spacingScale`: generates a single custom property per preset value. - `typography.fontSizes`: generates a single class and custom property per preset value. - `typography.fontFamilies`: generates a single custom property per preset value. diff --git a/docs/manifest.json b/docs/manifest.json index a2577530463555..1704e6d711510f 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -102,7 +102,7 @@ "parent": "fundamentals" }, { - "title": "Working with Javascript for the Block Editor", + "title": "Working with JavaScript for the Block Editor", "slug": "javascript-in-the-block-editor", "markdown_source": "../docs/getting-started/fundamentals/javascript-in-the-block-editor.md", "parent": "fundamentals" @@ -1121,6 +1121,12 @@ "markdown_source": "../packages/components/src/popover/README.md", "parent": "components" }, + { + "title": "ProgressBar", + "slug": "progress-bar", + "markdown_source": "../packages/components/src/progress-bar/README.md", + "parent": "components" + }, { "title": "QueryControls", "slug": "query-controls", diff --git a/docs/reference-guides/block-api/block-styles.md b/docs/reference-guides/block-api/block-styles.md index 90b6c06d18f59d..b47b1a76a71f68 100644 --- a/docs/reference-guides/block-api/block-styles.md +++ b/docs/reference-guides/block-api/block-styles.md @@ -1,6 +1,6 @@ # Styles -Block Styles allow alternative styles to be applied to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the block style is selected. See the [Getting Started with JavaScript tutorial](/docs/how-to-guides/javascript/) for a full example. +Block Styles allow alternative styles to be applied to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the block style is selected. See the [Use styles and stylesheets](/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md) for a full example on how to apply styles to a block. _Example:_ diff --git a/docs/reference-guides/block-api/block-variations.md b/docs/reference-guides/block-api/block-variations.md index 0440858810b65a..8a0c6b1dd5bd6c 100644 --- a/docs/reference-guides/block-api/block-variations.md +++ b/docs/reference-guides/block-api/block-variations.md @@ -1,12 +1,11 @@ # Variations -The Block Variations API allows you to define multiple versions (variations) of a block. A block variation differs from the original block by a set of initial attributes or inner blocks. When you insert the block variation into the Editor, these attributes and/or inner blocks are applied. +The Block Variations API allows you to define multiple versions (variations) of a block. A block variation differs from the original block by a set of initial attributes or inner blocks. When you insert the block variation into the Editor, these attributes and/or inner blocks are applied. Variations are an excellent way to create iterations of existing blocks without building entirely new blocks from scratch. To better understand this API, consider the Embed block. This block contains numerous variations for each type of embeddable content (WordPress, Youtube, etc.). Each Embed block variation shares the same underlying functionality for editing, saving, and so on. Besides the name and descriptive information, the main difference is the `providerNameSlug` attribute. Below is a simplified example of the variations in the Embed block. View the [source code](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/embed/variations.js) for the complete specification. - ```js variations: [ { @@ -38,7 +37,7 @@ A block variation is defined by an object that can contain the following fields: - `innerBlocks` (optional, type `Array[]`) – Initial configuration of nested blocks. - `example` (optional, type `Object`) – Provides structured data for the block preview. Set to `undefined` to disable the preview. See the [Block Registration API](/docs/reference-guides/block-api/block-registration.md#example-optional) for more details. - `scope` (optional, type `WPBlockVariationScope[]`) - Defaults to `block` and `inserter`. The list of scopes where the variation is applicable. Available options include: - - `block` - Used by blocks to filter specific block variations. `Columns` and `Query` blocks have such variations, which are passed to the [experimental BlockVariationPicker](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-variation-picker/README.md) component. This component handles displaying the variations and allows users to choose one of them. + - `block` - Used by blocks to filter specific block variations. `Columns` and `Query` blocks have such variations, which are passed to the [experimental BlockVariationPicker](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-variation-picker/README.md) component. This component handles displaying the variations and allows users to choose one of them. - `inserter` - Block variation is shown on the inserter. - `transform` - Block variation is shown in the component for variation transformations. - `isDefault` (optional, type `boolean`) – Defaults to `false`. Indicates whether the current variation is the default one (details below). @@ -55,13 +54,10 @@ Block variations can be declared during a block's registration by providing the To create a variation for an existing block, such as a Core block, use `wp.blocks.registerBlockVariation()`. This function accepts the name of the block and the object defining the variation. ```js -wp.blocks.registerBlockVariation( - 'core/embed', - { - name: 'custom-embed', - attributes: { providerNameSlug: 'custom' }, - } -); +wp.blocks.registerBlockVariation( 'core/embed', { + name: 'custom-embed', + attributes: { providerNameSlug: 'custom' }, +} ); ``` ## Removing a block variation @@ -102,17 +98,14 @@ By default, all variations will show up in the Inserter in addition to the origi For example, if you want Media & Text block to display the image on the right by default, you could create a variation like this: ```js - wp.blocks.registerBlockVariation( - 'core/media-text', - { - name: 'media-text-media-right', - title: __( 'Media & Text' ), - isDefault: true, - attributes: { - mediaPosition: 'right' - } - } -) +wp.blocks.registerBlockVariation( 'core/media-text', { + name: 'media-text-media-right', + title: __( 'Media & Text' ), + isDefault: true, + attributes: { + mediaPosition: 'right', + }, +} ); ``` ### Caveats to using `isDefault` @@ -129,9 +122,9 @@ While the `isActive` property is optional, it's recommended. This API is used by If `isActive` is not set, the Editor cannot distinguish between an instance of the original block and your variation, so the original block information will be displayed. -The property can be set to either a function or an array of strings (`string[]`). +The property can be set to either an array of strings (`string[]`), or a function. It is recommended to use the string array version whenever possible. -The function version of this property accepts a block instance's `blockAttributes` as the first argument, and the `variationAttributes` declared for a variation as the second argument. These arguments can be used to determine if a variation is active by comparing them and returning a `true` or `false` (indicating whether this variation is inactive for this block instance). +The `string[]` version is used to declare which of the block instance's attributes should be compared to the given variation's. Each attribute will be checked and the variation will be active if all of them match. As an example, in the core Embed block, the `providerNameSlug` attribute is used to determine the embed provider (e.g. 'youtube' or 'twitter'). The variations may be declared like this: @@ -159,53 +152,59 @@ const variations = [ attributes: { providerNameSlug: 'youtube', responsive: true }, }, // ... -] +]; ``` - The `isActive` function can compare the block instance value for `providerNameSlug` to the value declared in the variation's declaration (the values in the code snippet above) to determine which embed variation is active: +The `isActive` property would then look like this: ```js -isActive: ( blockAttributes, variationAttributes ) => - blockAttributes.providerNameSlug === variationAttributes.providerNameSlug, +isActive: [ 'providerNameSlug' ]; ``` -The `string[]` version is used to declare which attributes should be compared as a shorthand. Each attribute will be checked and the variation will be active if all of them match. Using the same example for the embed block, the string version would look like this: +This will cause the block instance value for `providerNameSlug` to be compared to the value declared in the variation's declaration (the values in the code snippet above) to determine which embed variation is active. + +Nested object paths are also supported since WordPress `6.6.0`. For example, consider a block variation that has a `query` object as an attribute. It is possible to determine if the variation is active solely based on that object's `postType` property (while ignoring all its other properties): ```js -isActive: [ 'providerNameSlug' ] +isActive: [ 'query.postType' ]; ``` -### Caveats to using `isActive` +The function version of this property accepts a block instance's `blockAttributes` as the first argument, and the `variationAttributes` declared for a variation as the second argument. These arguments can be used to determine if a variation is active by comparing them and returning a `true` or `false` (indicating whether this variation is inactive for this block instance). -The `isActive` property can return false positives if multiple variations exist for a specific block and the `isActive` checks are not specific enough. To demonstrate this, consider the following example: +Using the same example for the embed block, the function version would look like this: ```js -wp.blocks.registerBlockVariation( - 'core/paragraph', - { - name: 'paragraph-red', - title: 'Red Paragraph', - attributes: { - textColor: 'vivid-red', - }, - isActive: [ 'textColor' ], - } -); +isActive: ( blockAttributes, variationAttributes ) => + blockAttributes.providerNameSlug === variationAttributes.providerNameSlug, +``` -wp.blocks.registerBlockVariation( - 'core/paragraph', - { - name: 'paragraph-red-grey', - title: 'Red/Grey Paragraph', - attributes: { - textColor: 'vivid-red', - backgroundColor: 'cyan-bluish-gray' - }, - isActive: [ 'textColor', 'backgroundColor' ] - } -); +### Specificity of `isActive` matches + +_Note: Improved handling since WordPress `6.6.0`._ + +If there are multiple variations whose `isActive` check matches a given block instance, and all of them are string arrays, then the variation with the highest _specificity_ will be chosen. Consider the following example: + +```js +wp.blocks.registerBlockVariation( 'core/paragraph', { + name: 'paragraph-red', + title: 'Red Paragraph', + attributes: { + textColor: 'vivid-red', + }, + isActive: [ 'textColor' ], +} ); + +wp.blocks.registerBlockVariation( 'core/paragraph', { + name: 'paragraph-red-grey', + title: 'Red/Grey Paragraph', + attributes: { + textColor: 'vivid-red', + backgroundColor: 'cyan-bluish-gray', + }, + isActive: [ 'textColor', 'backgroundColor' ], +} ); ``` -The `isActive` check on both variations tests the `textColor`, but each variations uses `vivid-red`. Since the `paragraph-red` variation is registered first, once the `paragraph-red-grey` variation is inserted into the Editor, it will have the title `Red Paragraph` instead of `Red/Grey Paragraph`. As soon as the Editor finds a match, it stops checking. +If a block instance has attributes `textColor: vivid-red` and `backgroundColor: cyan-bluish-gray`, both variations' `isActive` criterion will match that block instance. In this case, the more _specific_ match will be determined to be the active variation, where specificity is calculated as the length of each `isActive` array. This means that the `Red/Grey Paragraph` will be shown as the active variation. -There have been [discussions](https://github.com/WordPress/gutenberg/issues/41303#issuecomment-1526193087) around how the API can be improved, but as of WordPress 6.3, this remains an issue to watch out for. +Note that specificity cannot be determined for a matching variation if its `isActive` property is a function rather than a `string[]`. In this case, the first matching variation will be determined to be the active variation. For this reason, it is generally recommended to use a `string[]` rather than a `function` for the `isActive` property. diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index c08869db34b484..93ab0a031800f0 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -245,7 +245,7 @@ Add an image or video with a text overlay. ([Source](https://github.com/WordPres - **Name:** core/cover - **Category:** media -- **Supports:** align, anchor, color (heading, text, ~~background~~, ~~enableContrastChecker~~), dimensions (aspectRatio), interactivity (clientNavigation), layout (~~allowJustification~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align, anchor, color (heading, text, ~~background~~, ~~enableContrastChecker~~), dimensions (aspectRatio), interactivity (clientNavigation), layout (~~allowJustification~~), shadow, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** allowedBlocks, alt, backgroundType, contentPosition, customGradient, customOverlayColor, dimRatio, focalPoint, gradient, hasParallax, id, isDark, isRepeated, isUserOverlayColor, minHeight, minHeightUnit, overlayColor, tagName, templateLock, url, useFeaturedImage ## Details @@ -616,7 +616,7 @@ Displays the contents of a post or page. ([Source](https://github.com/WordPress/ - **Name:** core/post-content - **Category:** theme -- **Supports:** align (full, wide), color (background, gradients, link, text), dimensions (minHeight), layout, spacing (blockGap), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradients, link, text), dimensions (minHeight), layout, spacing (blockGap, padding), typography (fontSize, lineHeight), ~~html~~ ## Date @@ -706,7 +706,7 @@ Give special visual emphasis to a quote from your text. ([Source](https://github - **Name:** core/pullquote - **Category:** text -- **Supports:** align (full, left, right, wide), anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight) +- **Supports:** align (full, left, right, wide), anchor, background (backgroundImage, backgroundSize), color (background, gradients, link, text), dimensions (minHeight), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight) - **Attributes:** citation, textAlign, value ## Query Loop @@ -783,7 +783,7 @@ Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Ju - **Name:** core/quote - **Category:** text -- **Supports:** anchor, color (background, gradients, heading, link, text), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** anchor, background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** citation, textAlign, value ## Read More @@ -948,7 +948,7 @@ Insert poetry. Use special spacing formats. Or quote song lyrics. ([Source](http - **Name:** core/verse - **Category:** text -- **Supports:** anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight) +- **Supports:** anchor, background (backgroundImage, backgroundSize), color (background, gradients, link, text), dimensions (minHeight), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight) - **Attributes:** content, textAlign ## Video diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 486fcddfe04ac6..4b66ad9eb6cb40 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -81,7 +81,6 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientId_ `string`: The block client Id. -- _rootClientId_ `?string`: Optional root client ID of block list. _Returns_ @@ -95,7 +94,6 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientIds_ `string`: The block client IDs to be moved. -- _rootClientId_ `?string`: Optional root client ID of block list. _Returns_ @@ -109,7 +107,6 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientId_ `string`: The block client Id. -- _rootClientId_ `?string`: Optional root client ID of block list. _Returns_ @@ -123,7 +120,6 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientIds_ `string`: The block client IDs to be removed. -- _rootClientId_ `?string`: Optional root client ID of block list. _Returns_ @@ -1439,7 +1435,7 @@ wp.data.dispatch( 'core/block-editor' ).registerInserterMediaCategory( { per_page: 'page_size', search: 'q', }; - const url = new URL( 'https://api.openverse.engineering/v1/images/' ); + const url = new URL( 'https://api.openverse.org/v1/images/' ); Object.entries( finalQuery ).forEach( ( [ key, value ] ) => { const queryKey = mapFromInserterMediaRequest[ key ] || key; url.searchParams.set( queryKey, value ); @@ -1841,11 +1837,11 @@ _Returns_ ### updateBlockListSettings -Action that changes the nested settings of a given block. +Action that changes the nested settings of the given block(s). _Parameters_ -- _clientId_ `string`: Client ID of the block whose nested setting are being received. +- _clientId_ `string | SettingsByClientId`: Client ID of the block whose nested setting are being received, or object of settings by client ID. - _settings_ `Object`: Object with the new settings for the nested block. _Returns_ diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 1f050ca98576a3..b22fd2238f3031 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -58,7 +58,7 @@ _Parameters_ _Returns_ -- `string?`: Post ID. +- `?string`: Post ID. ### getEditedPostType @@ -70,7 +70,7 @@ _Parameters_ _Returns_ -- `TemplateType?`: Template type. +- `?TemplateType`: Template type. ### getEditorMode diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md index a7b5d37da84643..4fea2c51fa54f3 100644 --- a/docs/reference-guides/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -485,7 +485,7 @@ _Returns_ ### getPermalinkParts -Returns the permalink for a post, split into it's three parts: the prefix, the postName, and the suffix. +Returns the permalink for a post, split into its three parts: the prefix, the postName, and the suffix. _Parameters_ diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md index 7fa20249760408..ee100431eb512e 100644 --- a/docs/reference-guides/filters/block-filters.md +++ b/docs/reference-guides/filters/block-filters.md @@ -1,61 +1,76 @@ # Block Filters -To modify the behavior of existing blocks, WordPress exposes several APIs. +WordPress exposes several APIs that allow you to modify the behavior of existing blocks. ## Registration -The following filters are available to extend the settings for blocks during their registration. +The following filters are available to extend block settings during their registration. ### `block_type_metadata` -Filters the raw metadata loaded from the `block.json` file when registering a block type on the server with PHP. It allows applying modifications before the metadata gets processed. +Filters the raw metadata loaded from the `block.json` file when registering a block type on the server with PHP. It allows modifications to be applied before the metadata gets processed. -The filter takes one param: +The filter takes one parameter: -- `$metadata` (`array`) – metadata loaded from `block.json` for registering a block type. +- `$metadata` (`array`) – metadata loaded from `block.json` for registering a block type. -_Example_: +The following example sets the `apiVersion` of all blocks to `2`. ```php - { @@ -233,7 +244,7 @@ const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => { Used to modify the block's wrapper component containing the block's `edit` component and all toolbars. It receives the original `BlockListBlock` component and returns a new wrapped component. -_Example:_ +The following example adds a unique class name. ```js const { createHigherOrderComponent } = wp.compose; @@ -259,10 +270,7 @@ wp.hooks.addFilter( ); ``` -Adding new properties to the block's wrapper component can be achieved by adding them to the `wrapperProps` property of the returned component. - -_Example:_ - +You can add new properties to the block's wrapper component using the `wrapperProps` property of the returned component as shown in the following example. ```js const { createHigherOrderComponent } = wp.compose; @@ -275,6 +283,7 @@ const withMyWrapperProp = createHigherOrderComponent( ( BlockListBlock ) => { return ; }; }, 'withMyWrapperProp' ); + wp.hooks.addFilter( 'editor.BlockListBlock', 'my-plugin/with-my-wrapper-prop', @@ -282,12 +291,11 @@ wp.hooks.addFilter( ); ``` - ### `editor.postContentBlockTypes` -Used to modify the list of blocks that should be enabled even when used inside a locked template. Any block that saves data to a post should be added here. Examples of this are the post featured image block. Which often gets used in templates but should still allow selecting the image even when the template is locked. +Used to modify the list of blocks that should be enabled even when used inside a locked template. Any block that saves data to a post should be added here. An example of this is the Post Featured Image block. Often used in templates, this block should still allow selecting the image even when the template is locked. -_Example:_ +The following example enables the fictitious block `namespace/example`. ```js const addExampleBlockToPostContentBlockTypes = ( blockTypes ) => { @@ -305,8 +313,9 @@ wp.hooks.addFilter( ### Using a deny list -Adding blocks is easy enough, removing them is as easy. Plugin or theme authors have the possibility to "unregister" blocks. +Adding blocks is easy enough, and removing them is as easy. Plugin or theme authors can "unregister" blocks using a deny list in JavaScript. +Place the following code in a `my-plugin.js` file. ```js // my-plugin.js @@ -318,8 +327,7 @@ domReady( function () { } ); ``` - -and load this script in the Editor +Then, load this script in the Editor using the following function. ```php + When unregistering a block, there can be a race condition on which code runs first: registering the block or unregistering the block. You want your unregister code to run last. To do this, you must specify the component that is registering the block as a dependency, in this case, wp-edit-post. Additionally, using wp.domReady() ensures the unregister code runs once the dom is loaded. + ### Using an allow list @@ -362,37 +372,37 @@ wp.blocks.getBlockTypes().forEach( function ( blockType ) { ### `allowed_block_types_all` -_**Note:** Before WordPress 5.8 known as `allowed_block_types`. In the case when you want to support older versions of WordPress you might need a way to detect which filter should be used – the deprecated one vs the new one. The recommended way to proceed is to check if the `WP_Block_Editor_Context` class exists._ +
+ Before WordPress 5.8, this hook was known as allowed_block_types, which is now deprecated. If you need to support older versions of WordPress, you might need a way to detect which filter should be used. You can check if allowed_block_types is safe to use by seeing if the WP_Block_Editor_Context class exists, which was introduced in 5.8. +
-On the server, you can filter the list of blocks shown in the inserter using the `allowed_block_types_all` filter. You can return either true (all block types supported), false (no block types supported), or an array of block type names to allow. You can also use the second provided param `$editor_context` to filter block types based on its content. +On the server, you can filter the list of blocks shown in the inserter using the `allowed_block_types_all` filter. You can return either true (all block types supported), false (no block types supported), or an array of block type names to allow. You can also use the second provided parameter `$editor_context` to filter block types based on their content. ```php post ) ) { return array( 'core/paragraph', 'core/heading' ); } return $allowed_block_types; } - -add_filter( 'allowed_block_types_all', 'wpdocs_filter_allowed_block_types_when_post_provided', 10, 2 ); +add_filter( 'allowed_block_types_all', 'example_filter_allowed_block_types_when_post_provided', 10, 2 ); ``` ## Managing block categories ### `block_categories_all` -_**Note:** Before WordPress 5.8 known as `block_categories`. In the case when you want to support older versions of WordPress you might need a way to detect which filter should be used – the deprecated one vs the new one. The recommended way to proceed is to check if the `WP_Block_Editor_Context` class exists._ +
+ Before WordPress 5.8, this hook was known as block_categories, which is now deprecated. If you need to support older versions of WordPress, you might need a way to detect which filter should be used. You can check if block_categories is safe to use by seeing if the WP_Block_Editor_Context class exists, which was introduced in 5.8. +
-It is possible to filter the list of default block categories using the `block_categories_all` filter. You can do it on the server by implementing a function which returns a list of categories. It is going to be used during blocks registration and to group blocks in the inserter. You can also use the second provided param `$editor_context` to filter the based on its content. +It is possible to filter the list of default block categories using the `block_categories_all` filter. You can do it on the server by implementing a function which returns a list of categories. It is going to be used during block registration and to group blocks in the inserter. You can also use the second provided parameter `$editor_context` to filter the based on its content. ```php -post ) ) { array_push( $block_categories, @@ -405,8 +415,7 @@ function wpdocs_filter_block_categories_when_post_provided( $block_categories, $ } return $block_categories; } - -add_filter( 'block_categories_all', 'wpdocs_filter_block_categories_when_post_provided', 10, 2 ); +add_filter( 'block_categories_all', 'example_filter_block_categories_when_post_provided', 10, 2 ); ``` ### `wp.blocks.updateCategory` diff --git a/docs/reference-guides/interactivity-api/README.md b/docs/reference-guides/interactivity-api/README.md index b6e0d639c3fc8b..85255af785cbb5 100644 --- a/docs/reference-guides/interactivity-api/README.md +++ b/docs/reference-guides/interactivity-api/README.md @@ -1,6 +1,6 @@ # 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. +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. @@ -32,7 +32,7 @@ To get a deeper understanding of what the Interactivity API is or find answers t 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: +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) @@ -55,7 +55,7 @@ To indicate that the block [supports](https://developer.wordpress.org/block-edit 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` +#### 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: @@ -82,7 +82,7 @@ The use of `viewScriptModule` also requires the `--experimental-modules` flag fo #### 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. +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. diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md index 043a50cb5186e6..8b56ed4ce98b41 100644 --- a/docs/reference-guides/slotfills/README.md +++ b/docs/reference-guides/slotfills/README.md @@ -33,7 +33,7 @@ registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); SlotFills are created using `createSlotFill`. This creates two components, `Slot` and `Fill` which are then used to create a new component that is exported on the `wp.plugins` global. -**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/HEAD/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js#L54)) +**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/HEAD/packages/editor/src/components/plugin-post-status-info/index.js#L55)) ```js /** @@ -61,34 +61,70 @@ export default PluginPostStatusInfo; This new Slot is then exposed in the editor. The example below is from core and represents the Summary panel. As we can see, the `` is wrapping all of the items that will appear in the panel. -Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed between the `` and `` components. +Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed in the end of the component. -See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/edit-post/src/components/sidebar/post-status/index.js#L26). +See [core code](https://github.com/WordPress/gutenberg/tree/HEAD/packages/editor/src/components/sidebar/post-summary.js#L39). ```js -const PostStatus = ( { isOpened, onTogglePanel } ) => ( - - - { ( fills ) => ( - <> - - - - - - - { fills } - - - ) } - - -); +export default function PostSummary( { onActionPerformed } ) { + const { isRemovedPostStatusPanel } = useSelect( ( select ) => { + // We use isEditorPanelRemoved to hide the panel if it was programmatically removed. We do + // not use isEditorPanelEnabled since this panel should not be disabled through the UI. + const { isEditorPanelRemoved, getCurrentPostType } = + select( editorStore ); + return { + isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ), + postType: getCurrentPostType(), + }; + }, [] ); + + return ( + + + { ( fills ) => ( + <> + + + } + /> + + + + + + + { ! isRemovedPostStatusPanel && ( + + + + + + + + + + + + + + + + + + { fills } + + ) } + + + ) } + + + ); +} ``` ## Currently available SlotFills and examples diff --git a/docs/reference-guides/slotfills/plugin-document-setting-panel.md b/docs/reference-guides/slotfills/plugin-document-setting-panel.md index d278a9e96981d1..aa3e55abe2f7ee 100644 --- a/docs/reference-guides/slotfills/plugin-document-setting-panel.md +++ b/docs/reference-guides/slotfills/plugin-document-setting-panel.md @@ -33,7 +33,7 @@ registerPlugin( 'plugin-document-setting-panel-demo', { ## Accessing a panel programmatically -Core and custom panels can be access programmatically using their panel name. The core panel names are: +Core and custom panels can be accessed programmatically using their panel name. The core panel names are: - Summary Panel: `post-status` - Categories Panel: `taxonomy-panel-category` 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 f752fe8104a568..59a820a16697c9 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -167,6 +167,7 @@ Settings related to spacing. | padding | boolean | false | | | units | array | px,em,rem,vh,vw,% | | | customSpacingSize | boolean | true | | +| defaultSpacingSizes | boolean | true | | | spacingSizes | array | | name, size, slug | | spacingScale | object | | | 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 c304bfe39493ee..8e9d56ed054e68 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-migrations.md +++ b/docs/reference-guides/theme-json-reference/theme-json-migrations.md @@ -88,8 +88,27 @@ The new `defaultFontSizes` option gives control over showing default font sizes 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`. + +#### `settings.spacing.defaultSpacingSizes` + +In theme.json v2, there are two settings that could be used to set theme level spacing sizes: `settings.spacing.spacingSizes` and `settings.spacing.spacingScale`. Setting both `spacingSizes` _and_ `spacingScale` would only use the values from `spacingSizes`. And setting either of them would always replace the entire set of default spacing sizes provided by WordPress. + +The default `spacingSizes` slugs provided by WordPress are: `20`, `30`, `40`, `50`, `60`, `70`, and `80`. + +The new `defaultSpacingSizes` option gives control over showing default spacing sizes and preventing those defaults from being overridden. + +- When set to `true` it will show the default spacing sizes and prevent them from being overridden by the theme. +- When set to `false` it will hide the default spacing sizes and allow the theme to use the default slugs. + +`defaultSpacingSizes` 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. + +Additionally, in v3 both `spacingSizes` and `spacingScale` can be set at the same time. Presets defined in `spacingSizes` with slugs matching the generated presets from `spacingSizes` will override the generated ones. + +To keep behavior similar to v2 with a v3 theme.json: +* If you do not have any `spacingSizes` presets or `spacingScale` config defined, `defaultSpacingSizes` can be left out or set to `true`. +* If you disabled default spacing sizes by setting `spacingScale` to `{ "steps": 0 }`, remove the `spacingScale` config and set `defaultSpacingSizes` to `false`. +* If you defined only one of either `spacingScale` or `spacingSizes` for your presets, set `defaultSpacingSizes` to `false`. +* If you defined both `spacingScale` and `spacingSizes`, remove the `spacingSizes` config _and_ set `defaultSpacingSizes` to `false`. diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index e830012bc57708..3c1f0fee2090ff 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -16,7 +16,6 @@ const componentPaths = glob( 'packages/components/src/*/**/README.md', { 'packages/components/src/theme/README.md', 'packages/components/src/view/README.md', 'packages/components/src/dropdown-menu-v2/README.md', - 'packages/components/src/progress-bar/README.md', 'packages/components/src/tabs/README.md', 'packages/components/src/custom-select-control-v2/README.md', ], diff --git a/gutenberg.php b/gutenberg.php index 0cf1a4f238a073..016b56b94dbc77 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.4 * Requires PHP: 7.2 - * Version: 18.4.0-rc.1 + * Version: 18.6.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/README.md b/lib/README.md index cbc86d67a18f7e..8e22f676a153d2 100644 --- a/lib/README.md +++ b/lib/README.md @@ -6,7 +6,7 @@ The Gutenberg plugin is continuously enhancing existing features and creating ne During a WordPress release, new features, bugfixes and other changes are "synced" between the Gutenberg plugin and WordPress Core. Consistent naming and directory structures make this process easier by preventing naming conflicts and compartmentalizing release-specific code. -The following documentation is intended to act as a guide only. If you're unsure about naming or where to place new PHP files, please don't hesitate to ping other contributors on Github or ask in the #core-editor channel on [WordPress Slack](https://make.wordpress.org/chat/). +The following documentation is intended to act as a guide only. If you're unsure about naming or where to place new PHP files, please don't hesitate to ping other contributors on GitHub or ask in the #core-editor channel on [WordPress Slack](https://make.wordpress.org/chat/). ## File structure @@ -194,10 +194,10 @@ Existing comments in `lib/load.php` should act as a guide. If you've changed or added PHP files to the Gutenberg plugin, you'll need to confirm whether the changes are to be synced to WordPress Core, and therefore featured in the next release of WordPress. -The Gutenberg Github pull request in question should be labeled with the `Needs PHP backport` label if the changes are to be synced to Core. +The Gutenberg GitHub pull request in question should be labeled with the `Needs PHP backport` label if the changes are to be synced to Core. -If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core Github repository](https://github.com/WordPress/wordpress-develop) soon after your pull request is merged. +If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core GitHub repository](https://github.com/WordPress/wordpress-develop) soon after your pull request is merged. -So too, if you've made changes in WordPress Core to code that also lives in the Gutenberg plugin, these changes will need to be synced (often called "backporting") to Gutenberg. The relevant Gutenberg Github pull request should be labeled with the `Backport from WordPress Core` label. +So too, if you've made changes in WordPress Core to code that also lives in the Gutenberg plugin, these changes will need to be synced (often called "backporting") to Gutenberg. The relevant Gutenberg GitHub pull request should be labeled with the `Backport from WordPress Core` label. If you're unsure, you can always ask for help in the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). diff --git a/lib/block-editor-settings.php b/lib/block-editor-settings.php index 53668e114e04cb..defd7cd391b16b 100644 --- a/lib/block-editor-settings.php +++ b/lib/block-editor-settings.php @@ -58,7 +58,7 @@ function gutenberg_get_block_editor_settings( $settings ) { * entered by users does not break other global styles. */ $global_styles[] = array( - 'css' => gutenberg_get_global_styles_custom_css(), + 'css' => gutenberg_get_global_stylesheet( array( 'custom-css' ) ), '__unstableType' => 'user', 'isGlobalStyles' => true, ); diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php new file mode 100644 index 00000000000000..e078b50b19d5a3 --- /dev/null +++ b/lib/block-supports/block-style-variations.php @@ -0,0 +1,518 @@ +get_raw_data(); + + // Only the first block style variation with data is supported. + $variation_data = array(); + foreach ( $variations as $variation ) { + $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + + if ( ! empty( $variation_data ) ) { + break; + } + } + + if ( empty( $variation_data ) ) { + return $parsed_block; + } + + $variation_instance = gutenberg_create_block_style_variation_instance_name( $parsed_block, $variation ); + $class_name = "is-style-$variation_instance"; + $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; + + /* + * Even though block style variations are effectively theme.json partials, + * they can't be processed completely as though they are. + * + * Block styles support custom selectors to direct specific types of styles + * to inner elements. For example, borders on Image block's get applied to + * the inner `img` element rather than the wrapping `figure`. + * + * The following relocates the "root" block style variation styles to + * under an appropriate blocks property to leverage the preexisting style + * generation for simple block style variations. This way they get the + * custom selectors they need. + * + * The inner elements and block styles for the variation itself are + * still included at the top level but scoped by the variation's selector + * when the stylesheet is generated. + */ + $elements_data = $variation_data['elements'] ?? array(); + $blocks_data = $variation_data['blocks'] ?? array(); + unset( $variation_data['elements'] ); + unset( $variation_data['blocks'] ); + + _wp_array_set( + $blocks_data, + array( $parsed_block['blockName'], 'variations', $variation_instance ), + $variation_data + ); + + $config = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'elements' => $elements_data, + 'blocks' => $blocks_data, + ), + ); + + // Turn off filter that excludes block nodes. They are needed here for the variation's inner block types. + if ( ! is_admin() ) { + remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); + } + + // Temporarily prevent variation instance from being sanitized while processing theme.json. + $styles_registry = WP_Block_Styles_Registry::get_instance(); + $styles_registry->register( $parsed_block['blockName'], array( 'name' => $variation_instance ) ); + + $variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' ); + $variation_styles = $variation_theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'skip_root_layout_styles' => true, + 'scope' => ".$class_name", + ) + ); + + // Clean up temporary block style now instance styles have been processed. + $styles_registry->unregister( $parsed_block['blockName'], $variation_instance ); + + // Restore filter that excludes block nodes. + if ( ! is_admin() ) { + add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); + } + + if ( empty( $variation_styles ) ) { + return $parsed_block; + } + + wp_register_style( 'block-style-variation-styles', false, array( 'global-styles', 'wp-block-library' ) ); + wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); + + /* + * Add variation instance class name to block's className string so it can + * be enforced in the block markup via render_block filter. + */ + _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name ); + + return $parsed_block; +} + +/** + * Ensure the variation 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_block_style_variation_support_styles + * + * @since 6.6.0 + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * + * @return string Filtered block content. + */ +function gutenberg_render_block_style_variation_class_name( $block_content, $block ) { + if ( ! $block_content || empty( $block['attrs']['className'] ) ) { + return $block_content; + } + + /* + * Matches a class prefixed by `is-style`, followed by the + * variation slug, then `--`, and finally a hash. + * + * See `gutenberg_create_block_style_variation_instance_name` for class generation. + */ + preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); + + if ( empty( $matches ) ) { + return $block_content; + } + + $tags = new WP_HTML_Tag_Processor( $block_content ); + + if ( $tags->next_tag() ) { + /* + * Ensure the variation instance class name set in the + * `render_block_data` filter is applied in markup. + * See `gutenberg_render_block_style_variation_support_styles`. + */ + $tags->add_class( $matches[0] ); + } + + return $tags->get_updated_html(); +} + +/** + * Collects block style variation data for merging with theme.json data. + * + * @since 6.6.0 + * + * @param array $variations Shared block style variations. + * + * @return array Block variations data to be merged under `styles.blocks`. + */ +function gutenberg_resolve_block_style_variations( $variations ) { + $variations_data = array(); + + if ( empty( $variations ) ) { + return $variations_data; + } + + $have_named_variations = ! wp_is_numeric_array( $variations ); + + foreach ( $variations as $key => $variation ) { + $supported_blocks = $variation['blockTypes'] ?? array(); + + /* + * Standalone theme.json partial files for block style variations + * will have their styles under a top-level property by the same name. + * Variations defined within an existing theme.json or theme style + * variation will themselves already be the required styles data. + */ + $variation_data = $variation['styles'] ?? $variation; + + if ( empty( $variation_data ) ) { + continue; + } + + /* + * Block style variations read in via standalone theme.json partials + * need to have their name set to the kebab case version of their title. + */ + $variation_name = $have_named_variations ? $key : ( $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ) ); + + foreach ( $supported_blocks as $block_type ) { + // Add block style variation data under current block type. + $path = array( $block_type, 'variations', $variation_name ); + _wp_array_set( $variations_data, $path, $variation_data ); + } + } + + return $variations_data; +} + +/** + * Merges variations data with existing theme.json data ensuring that the + * current theme.json data values take precedence. + * + * @since 6.6.0 + * + * @param array $variations_data Block style variations data keyed by block type. + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * @param string $origin Origin for the theme.json data. + * + * @return WP_Theme_JSON_Gutenberg The merged theme.json data. + */ +function gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, $origin = 'theme' ) { + if ( empty( $variations_data ) ) { + return $theme_json; + } + + $variations_theme_json_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( 'blocks' => $variations_data ), + ); + + $variations_theme_json = new WP_Theme_JSON_Data_Gutenberg( $variations_theme_json_data, $origin ); + + /* + * Merge the current theme.json data over shared variation data so that + * any explicit per block variation values take precedence. + */ + return $variations_theme_json->update_with( $theme_json->get_data() ); +} + +/** + * Merges any shared block style variation definitions from a theme style + * variation into their appropriate block type within theme json styles. Any + * custom user selections already made will take precedence over the shared + * style variation value. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_theme_style_variation( $theme_json ) { + $theme_json_data = $theme_json->get_data(); + $shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); + $variations_data = gutenberg_resolve_block_style_variations( $shared_variations ); + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json, 'user' ); +} + +/** + * Merges block style variation data sourced from standalone partial + * theme.json files. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_theme_json_partials( $theme_json ) { + $block_style_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' ); + $variations_data = gutenberg_resolve_block_style_variations( $block_style_variations ); + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); +} + +/** + * Merges shared block style variations registered within the + * `styles.blocks.variations` property of the primary theme.json file. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_primary_theme_json( $theme_json ) { + $theme_json_data = $theme_json->get_data(); + $block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); + $variations_data = gutenberg_resolve_block_style_variations( $block_style_variations ); + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); +} + +/** + * Merges block style variations registered via the block styles registry with a + * style object, under their appropriate block types within theme.json styles. + * Any variation values defined within the theme.json specific to a block type + * will take precedence over these shared definitions. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Data_Gutenberg $theme_json Current theme.json data. + * + * @return WP_Theme_JSON_Data_Gutenberg + */ +function gutenberg_resolve_block_style_variations_from_styles_registry( $theme_json ) { + $registry = WP_Block_Styles_Registry::get_instance(); + $styles = $registry->get_all_registered(); + $variations_data = array(); + + foreach ( $styles as $block_type => $variations ) { + foreach ( $variations as $variation_name => $variation ) { + if ( ! empty( $variation['style_data'] ) ) { + $path = array( $block_type, 'variations', $variation_name ); + _wp_array_set( $variations_data, $path, $variation['style_data'] ); + } + } + } + + return gutenberg_merge_block_style_variations_data( $variations_data, $theme_json ); +} + +/** + * Enqueues styles for block style variations. + * + * @since 6.6.0 + */ +function gutenberg_enqueue_block_style_variation_styles() { + wp_enqueue_style( 'block-style-variation-styles' ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( 'block-style-variation', array() ); + +// Remove core filters and action. +if ( function_exists( 'wp_render_block_style_variation_support_styles' ) ) { + remove_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles' ); +} +if ( function_exists( 'wp_render_block_style_variation_class_name' ) ) { + remove_filter( 'render_block', 'wp_render_block_style_variation_class_name' ); +} +if ( function_exists( 'wp_enqueue_block_style_variation_styles' ) ) { + remove_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles' ); +} + +if ( function_exists( 'wp_resolve_block_style_variations_from_primary_theme_json' ) ) { + remove_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_primary_theme_json' ); +} +if ( function_exists( 'wp_resolve_block_style_variations_from_theme_json_partials' ) ) { + remove_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_theme_json_partials' ); +} +if ( function_exists( 'wp_resolve_block_style_variations_from_styles_registry' ) ) { + remove_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_styles_registry' ); +} +if ( function_exists( 'wp_resolve_block_style_variations_from_theme_style_variation' ) ) { + remove_filter( 'wp_theme_json_data_user', 'wp_resolve_block_style_variations_from_theme_style_variation' ); +} + +// Add Gutenberg filters and action. +add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles', 10, 2 ); +add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 ); +add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 ); + +// Resolve block style variations from all their potential sources. The order here is deliberate. +add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_primary_theme_json', 10, 1 ); +add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_theme_json_partials', 10, 1 ); +add_filter( 'wp_theme_json_data_theme', 'gutenberg_resolve_block_style_variations_from_styles_registry', 10, 1 ); + +add_filter( 'wp_theme_json_data_user', 'gutenberg_resolve_block_style_variations_from_theme_style_variation', 10, 1 ); + + +/** + * Registers any block style variations contained within the provided + * theme.json data. + * + * @access private + * + * @param array $variations Shared block style variations. + */ +function gutenberg_register_block_style_variations_from_theme_json_data( $variations ) { + if ( empty( $variations ) ) { + return; + } + + $registry = WP_Block_Styles_Registry::get_instance(); + $have_named_variations = ! wp_is_numeric_array( $variations ); + + foreach ( $variations as $key => $variation ) { + $supported_blocks = $variation['blockTypes'] ?? array(); + + /* + * Standalone theme.json partial files for block style variations + * will have their styles under a top-level property by the same name. + * Variations defined within an existing theme.json or theme style + * variation will themselves already be the required styles data. + */ + $variation_data = $variation['styles'] ?? $variation; + + if ( empty( $variation_data ) ) { + continue; + } + + /* + * Block style variations read in via standalone theme.json partials + * need to have their name set to the kebab case version of their title. + */ + $variation_name = $have_named_variations ? $key : ( $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ) ); + $variation_label = $variation['title'] ?? $variation_name; + + foreach ( $supported_blocks as $block_type ) { + $registered_styles = $registry->get_registered_styles_for_block( $block_type ); + + // Register block style variation if it hasn't already been registered. + if ( ! array_key_exists( $variation_name, $registered_styles ) ) { + register_block_style( + $block_type, + array( + 'name' => $variation_name, + 'label' => $variation_label, + ) + ); + } + } + } +} + +/** + * Register shared block style variations defined by the theme. + * + * These can come in three forms: + * - the theme's theme.json + * - the theme's partials (standalone files in `/styles` that only define block style variations) + * - the user's theme.json (for example, theme style variations the user selected) + * + * @access private + */ +function gutenberg_register_block_style_variations_from_theme() { + // Partials from `/styles`. + $variations_partials = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' ); + gutenberg_register_block_style_variations_from_theme_json_data( $variations_partials ); + + /* + * Pull the data from the specific origin instead of the merged data. + * This is because, for 6.6, we only support registering block style variations + * for the 'theme' and 'custom' origins but not for 'default' (core theme.json) + * or 'custom' (theme.json in a block). + * + * When/If we add support for every origin, we should switch to using the public API + * instead, e.g.: wp_get_global_styles( array( 'blocks', 'variations' ) ). + */ + + // theme.json of the theme. + $theme_json_theme = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data(); + $variations_theme = $theme_json_theme->get_data()['styles']['blocks']['variations'] ?? array(); + gutenberg_register_block_style_variations_from_theme_json_data( $variations_theme ); + + // User data linked for this theme. + $theme_json_user = WP_Theme_JSON_Resolver_Gutenberg::get_user_data(); + $variations_user = $theme_json_user->get_data()['styles']['blocks']['variations'] ?? array(); + gutenberg_register_block_style_variations_from_theme_json_data( $variations_user ); +} + +// Remove core init action registering variations. +if ( function_exists( 'wp_register_block_style_variations_from_theme' ) ) { + remove_action( 'init', 'wp_register_block_style_variations_from_theme' ); +} +add_action( 'init', 'gutenberg_register_block_style_variations_from_theme' ); diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index aac62e402148a3..dc5fe2e6a87dae 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -577,7 +577,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { // Child layout specific logic. if ( $child_layout ) { - $container_content_class = wp_unique_id( 'wp-container-content-' ); + $container_content_class = wp_unique_prefixed_id( 'wp-container-content-' ); $child_layout_declarations = array(); $child_layout_styles = array(); diff --git a/lib/block-template-utils.php b/lib/block-template-utils.php new file mode 100644 index 00000000000000..a644047d3cfdc1 --- /dev/null +++ b/lib/block-template-utils.php @@ -0,0 +1,114 @@ +open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { + return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.', 'gutenberg' ) ); + } + + $zip->addEmptyDir( 'templates' ); + $zip->addEmptyDir( 'parts' ); + + // Get path of the theme. + $theme_path = wp_normalize_path( get_stylesheet_directory() ); + + // Create recursive directory iterator. + $theme_files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $theme_path ), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + // Make a copy of the current theme. + foreach ( $theme_files as $file ) { + // Skip directories as they are added automatically. + if ( ! $file->isDir() ) { + // Get real and relative path for current file. + $file_path = wp_normalize_path( $file ); + $relative_path = substr( $file_path, strlen( $theme_path ) + 1 ); + + if ( ! wp_is_theme_directory_ignored( $relative_path ) ) { + $zip->addFile( $file_path, $relative_path ); + } + } + } + + // Load templates into the zip file. + $templates = gutenberg_get_block_templates(); + foreach ( $templates as $template ) { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + '_remove_theme_attribute_from_template_part_block' + ); + + $zip->addFromString( + 'templates/' . $template->slug . '.html', + $template->content + ); + } + + // Load template parts into the zip file. + $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' ); + foreach ( $template_parts as $template_part ) { + $zip->addFromString( + 'parts/' . $template_part->slug . '.html', + $template_part->content + ); + } + + // Load theme.json into the zip file. + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) ); + // Merge with user data. + $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() ); + + $theme_json_raw = $tree->get_data(); + // If a version is defined, add a schema. + if ( $theme_json_raw['version'] ) { + $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 ); + $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' ); + $theme_json_raw = array_merge( $schema, $theme_json_raw ); + } + + // Convert to a string. + $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); + + // Replace 4 spaces with a tab. + $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); + + // Add the theme.json file to the zip. + $zip->addFromString( + 'theme.json', + $theme_json_tabbed + ); + + // Save changes to the zip file. + $zip->close(); + + return $filename; +} diff --git a/lib/blocks.php b/lib/blocks.php index 679219cc6ff774..c3fdb26700c58c 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -48,6 +48,7 @@ function gutenberg_reregister_core_block_types() { 'archives.php' => 'core/archives', 'avatar.php' => 'core/avatar', 'block.php' => 'core/block', + 'button.php' => 'core/button', 'calendar.php' => 'core/calendar', 'categories.php' => 'core/categories', 'cover.php' => 'core/cover', diff --git a/lib/class-wp-rest-edit-site-export-controller-gutenberg.php b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php new file mode 100644 index 00000000000000..b05de230dd0ccd --- /dev/null +++ b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php @@ -0,0 +1,46 @@ +add_data( array( 'status' => 500 ) ); + + return $filename; + } + + $theme_name = basename( get_stylesheet() ); + header( 'Content-Type: application/zip' ); + header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' ); + header( 'Content-Length: ' . filesize( $filename ) ); + flush(); + readfile( $filename ); + unlink( $filename ); + exit; + } +} diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 79cb8af59df4f9..aadcb2fd7de1e3 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -1,6 +1,6 @@ post_content, true ); $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON']; $config = array(); + $theme_json = null; if ( $is_global_styles_user_theme_json ) { - $config = ( new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ) )->get_raw_data(); + $theme_json = new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ); + $config = $theme_json->get_raw_data(); } // Base fields for every post. @@ -409,6 +431,13 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $post->ID ); + // Only return resolved URIs for get requests to user theme JSON. + if ( $theme_json ) { + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); + if ( ! empty( $resolved_theme_uris ) ) { + $links['https://api.w.org/theme-file'] = $resolved_theme_uris; + } + } $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions(); @@ -620,18 +649,22 @@ public function get_theme_item( $request ) { $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); } - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); - + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { - $links = array( + $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ), ), ); + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); + if ( ! empty( $resolved_theme_uris ) ) { + $links['https://api.w.org/theme-file'] = $resolved_theme_uris; + } + $response->add_links( $links ); } @@ -671,6 +704,7 @@ public function get_theme_items_permissions_check( $request ) { // phpcs:ignore * @since 6.0.0 * @since 6.2.0 Returns parent theme variations, if they exist. * @since 6.4.0 Removed unnecessary local variable. + * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item. * * @param WP_REST_Request $request The request instance. * @@ -686,9 +720,25 @@ public function get_theme_items( $request ) { ); } + $response = array(); $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); - return rest_ensure_response( $variations ); + // Add resolved theme asset links. + foreach ( $variations as $variation ) { + $variation_theme_json = new WP_Theme_JSON_Gutenberg( $variation ); + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $variation_theme_json ); + $data = rest_ensure_response( $variation ); + if ( ! empty( $resolved_theme_uris ) ) { + $data->add_links( + array( + 'https://api.w.org/theme-file' => $resolved_theme_uris, + ) + ); + } + $response[] = $this->prepare_response_for_collection( $data ); + } + + return rest_ensure_response( $response ); } /** diff --git a/lib/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php index c564016b1a7119..2c7eec9d5dbf4a 100644 --- a/lib/class-wp-theme-json-data-gutenberg.php +++ b/lib/class-wp-theme-json-data-gutenberg.php @@ -68,4 +68,15 @@ public function update_with( $new_data ) { public function get_data() { return $this->theme_json->get_raw_data(); } + + /** + * Return theme JSON object. + * + * @since 18.5.0 + * + * @return WP_Theme_JSON + */ + public function get_theme_json() { + return $this->theme_json; + } } diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1dec7b164d880b..deb4d850d271fb 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -123,6 +123,7 @@ class WP_Theme_JSON_Gutenberg { * @since 6.0.0 Replaced `override` with `prevent_override` and updated the * `prevent_override` value for `color.duotone` to use `color.defaultDuotone`. * @since 6.2.0 Added 'shadow' presets. + * @since 6.6.0 Updated the 'prevent_override' value for font size presets to use 'typography.defaultFontSizes' and spacing size presets to use `spacing.defaultSpacingSizes`. * @since 6.6.0 Added `aspectRatios`. * @var array */ @@ -187,7 +188,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'spacing', 'spacingSizes' ), - 'prevent_override' => false, + 'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ), 'use_default_names' => true, 'value_key' => 'size', 'css_vars' => '--wp--preset--spacing--$slug', @@ -348,6 +349,7 @@ class WP_Theme_JSON_Gutenberg { * @var string[] */ const VALID_TOP_LEVEL_KEYS = array( + 'blockTypes', 'customTemplates', 'description', 'patterns', @@ -355,6 +357,7 @@ class WP_Theme_JSON_Gutenberg { 'styles', 'templateParts', 'title', + 'slug', 'version', ); @@ -426,13 +429,14 @@ class WP_Theme_JSON_Gutenberg { 'sticky' => null, ), 'spacing' => array( - 'customSpacingSize' => null, - 'spacingSizes' => null, - 'spacingScale' => null, - 'blockGap' => null, - 'margin' => null, - 'padding' => null, - 'units' => null, + 'customSpacingSize' => null, + 'defaultSpacingSizes' => null, + 'spacingSizes' => null, + 'spacingScale' => null, + 'blockGap' => null, + 'margin' => null, + 'padding' => null, + 'units' => null, ), 'shadow' => array( 'presets' => null, @@ -726,23 +730,25 @@ public static function get_element_class_name( $element ) { * Constructor. * * @since 5.8.0 + * @since 6.6.0 Key spacingScale by origin, and pre-generate the + * spacingSizes from spacingScale. * * @param array $theme_json A structure that follows the theme.json schema. * @param string $origin Optional. What source of data this object represents. - * One of 'default', 'theme', or 'custom'. Default 'theme'. + * One of 'blocks', 'default', 'theme', or 'custom'. Default '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'; } - $this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json ); + $this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin ); $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 = 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 ); + $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); + $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); // Internally, presets are keyed by origin. $nodes = static::get_setting_nodes( $this->theme_json ); @@ -761,6 +767,27 @@ public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gut } } } + + // In addition to presets, spacingScale (which generates presets) is also keyed by origin. + $scale_path = array( 'settings', 'spacing', 'spacingScale' ); + $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); + if ( null !== $spacing_scale ) { + // If the spacingScale is not already keyed by origin. + if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) { + _wp_array_set( $this->theme_json, $scale_path, array( $origin => $spacing_scale ) ); + } + } + + // Pre-generate the spacingSizes from spacingScale. + $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); + $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); + if ( isset( $spacing_scale ) ) { + $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); + $spacing_sizes = _wp_array_get( $this->theme_json, $sizes_path, array() ); + $spacing_scale_sizes = static::compute_spacing_sizes( $spacing_scale ); + $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); + _wp_array_set( $this->theme_json, $sizes_path, $merged_spacing_sizes ); + } } /** @@ -816,6 +843,7 @@ protected static function do_opt_in_into_settings( &$context ) { * * @since 5.8.0 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. + * @since 6.6.0 Extended schema definition to allow enhanced block style variations. * * @param array $input Structure to sanitize. * @param array $valid_block_names List of valid block names. @@ -874,6 +902,27 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks = array(); $schema_settings_blocks = array(); + + /* + * Generate a schema for blocks. + * - Block styles can contain `elements` & `variations` definitions. + * - Variations definitions cannot be nested. + * - Variations can contain styles for inner `blocks`. + * - Variation inner `blocks` styles can contain `elements`. + * + * As each variation needs a `blocks` schema but further nested + * inner `blocks`, the overall schema will be generated in multiple passes. + */ + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = $styles_non_top_level; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + } + + $block_style_variation_styles = static::VALID_STYLES; + $block_style_variation_styles['blocks'] = $schema_styles_blocks; + $block_style_variation_styles['elements'] = $schema_styles_elements; + foreach ( $valid_block_names as $block ) { // Build the schema for each block style variation. $style_variation_names = array(); @@ -890,12 +939,9 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_variations = array(); if ( ! empty( $style_variation_names ) ) { - $schema_styles_variations = array_fill_keys( $style_variation_names, $styles_non_top_level ); + $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); } - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; } @@ -906,6 +952,12 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema['settings']['blocks'] = $schema_settings_blocks; $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); + /* + * Shared block style variations can be registered from the theme.json data so we can't + * validate them against pre-registered block style variations. + */ + $schema['styles']['blocks']['variations'] = null; + // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { if ( ! isset( $input[ $subtree ] ) ) { @@ -1008,17 +1060,37 @@ protected static function prepend_to_selector( $selector, $to_prepend ) { * @since 5.8.0 * @since 5.9.0 Added `duotone` key with CSS selector. * @since 6.1.0 Added `features` key with block support feature level selectors. + * @since 6.6.0 Added non-core block style variations to generated metadata. * * @return array Block metadata. */ protected static function get_blocks_metadata() { // NOTE: the compat/6.1 version of this method in Gutenberg did not have these changes. - $registry = WP_Block_Type_Registry::get_instance(); - $blocks = $registry->get_all_registered(); + $registry = WP_Block_Type_Registry::get_instance(); + $blocks = $registry->get_all_registered(); + $style_registry = WP_Block_Styles_Registry::get_instance(); // Is there metadata for all currently registered blocks? $blocks = array_diff_key( $blocks, static::$blocks_metadata ); if ( empty( $blocks ) ) { + /* + * New block styles may have been registered within WP_Block_Styles_Registry. + * Update block metadata for any new block style variations. + */ + $registered_styles = $style_registry->get_all_registered(); + foreach ( static::$blocks_metadata as $block_name => $block_metadata ) { + if ( ! empty( $registered_styles[ $block_name ] ) ) { + $style_selectors = $block_metadata['styleVariations'] ?? array(); + + foreach ( $registered_styles[ $block_name ] as $block_style ) { + if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { + $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); + } + } + + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + } + } return static::$blocks_metadata; } @@ -1051,11 +1123,20 @@ protected static function get_blocks_metadata() { } // If the block has style variations, append their selectors to the block metadata. + $style_selectors = array(); if ( ! empty( $block_type->styles ) ) { - $style_selectors = array(); foreach ( $block_type->styles as $style ) { $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } + } + + // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. + $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); + foreach ( $registered_styles as $style ) { + $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + } + + if ( ! empty( $style_selectors ) ) { static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } @@ -1167,6 +1248,7 @@ public function get_settings() { * * @since 5.8.0 * @since 5.9.0 Removed the `$type` parameter`, added the `$types` and `$origins` parameters. + * @since 6.6.0 Added option to skip root layout styles. * * @param array $types Types of styles to load. Will load all by default. It accepts: * - `variables`: only the CSS Custom Properties for presets & custom ones. @@ -1174,8 +1256,10 @@ public function get_settings() { * - `presets`: only the classes for the presets. * @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options An array of options for now used for internal purposes only (may change without notice). - * The options currently supported are 'scope' that makes sure all style are scoped to a given selector, - * and root_selector which overwrites and forces a given selector to be used on the root node. + * The options currently supported are: + * - 'scope' that makes sure all style are scoped to a given selector + * - `root_selector` which overwrites and forces a given selector to be used on the root node + * - `skip_root_layout_styles` which omits root layout styles from the generated stylesheet. * @return string The resulting stylesheet. */ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null, $options = array() ) { @@ -1228,7 +1312,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } if ( in_array( 'styles', $types, true ) ) { - if ( false !== $root_style_key ) { + if ( false !== $root_style_key && empty( $options['skip_root_layout_styles'] ) ) { $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] ); } $stylesheet .= $this->get_block_classes( $style_nodes ); @@ -1272,6 +1356,12 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } + // Load the custom CSS last so it has the highest specificity. + if ( in_array( 'custom-css', $types, true ) ) { + // Add the global styles root CSS. + $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); + } + return $stylesheet; } @@ -1293,7 +1383,7 @@ protected function process_blocks_custom_css( $css, $selector ) { $is_root_css = ( ! str_contains( $part, '{' ) ); if ( $is_root_css ) { // If the part doesn't contain braces, it applies to the root level. - $processed_css .= trim( $selector ) . '{' . trim( $part ) . '}'; + $processed_css .= ':root :where(' . trim( $selector ) . '){' . trim( $part ) . '}'; } else { // If the part contains braces, it's a nested CSS rule. $part = explode( '{', str_replace( '}', '', $part ) ); @@ -1305,7 +1395,8 @@ protected function process_blocks_custom_css( $css, $selector ) { $part_selector = str_starts_with( $nested_selector, ' ' ) ? static::scope_selector( $selector, $nested_selector ) : static::append_to_selector( $selector, $nested_selector ); - $processed_css .= $part_selector . '{' . trim( $css_value ) . '}'; + $final_selector = ":root :where($part_selector)"; + $processed_css .= $final_selector . '{' . trim( $css_value ) . '}'; } } return $processed_css; @@ -1315,13 +1406,16 @@ protected function process_blocks_custom_css( $css, $selector ) { * Returns the global styles custom css. * * @since 6.2.0 + * @deprecated 6.7.0 Use {@see 'get_stylesheet'} instead. * * @return string The global styles custom CSS. */ public function get_custom_css() { + _deprecated_function( __METHOD__, '6.7.0', 'get_stylesheet' ); $block_custom_css = ''; $block_nodes = $this->get_block_custom_css_nodes(); foreach ( $block_nodes as $node ) { + // The node selector will have its specificity set to 0-1-0 within process_blocks_custom_css. $block_custom_css .= $this->get_block_custom_css( $node['css'], $node['selector'] ); } @@ -1330,23 +1424,23 @@ public function get_custom_css() { /** * Returns the global styles base custom CSS. - * - * @since 6.6.0 + * This function is deprecated; please do not sync to core. * * @return string The global styles base custom CSS. */ public function get_base_custom_css() { + _deprecated_function( __METHOD__, 'Gutenberg 18.6.0', 'get_stylesheet' ); return isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : ''; } /** * Returns the block nodes with custom CSS. - * - * @since 6.6.0 + * This function is deprecated; please do not sync to core. * * @return array The block nodes. */ public function get_block_custom_css_nodes() { + _deprecated_function( __METHOD__, 'Gutenberg 18.6.0', 'get_block_nodes' ); $block_nodes = array(); // Add the global styles block CSS. @@ -1370,8 +1464,7 @@ public function get_block_custom_css_nodes() { /** * Returns the global styles custom CSS for a single block. - * - * @since 6.6.0 + * This function is deprecated; please do not sync to core. * * @param array $css The block css node. * @param string $selector The block selector. @@ -1379,6 +1472,7 @@ public function get_block_custom_css_nodes() { * @return string The global styles custom CSS for the block. */ public function get_block_custom_css( $css, $selector ) { + _deprecated_function( __METHOD__, 'Gutenberg 18.6.0', 'get_styles_for_block' ); return $this->process_blocks_custom_css( $css, $selector ); } @@ -1564,7 +1658,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) { $spacing_rule['selector'] ); } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s) %3$s' : ':where(%1$s-%2$s) %3$s'; + $format = static::ROOT_BLOCK_SELECTOR === $selector ? '.%2$s %3$s' : '%1$s-%2$s %3$s'; $layout_selector = sprintf( $format, $selector, @@ -2561,6 +2655,7 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { 'selectors' => $feature_selectors, 'duotone' => $duotone_selector, 'variations' => $variation_selectors, + 'css' => $selector, ); if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { @@ -2609,6 +2704,7 @@ public function get_styles_for_block( $block_metadata ) { // If there are style variations, generate the declarations for them, including any feature selectors the block may have. $style_variation_declarations = array(); + $style_variation_custom_css = array(); if ( ! empty( $block_metadata['variations'] ) ) { foreach ( $block_metadata['variations'] as $style_variation ) { $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); @@ -2635,9 +2731,12 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) { // Add the new declarations to the overall results under the modified selector. $style_variation_declarations[ $combined_selectors ] = $new_declarations; } - // Compute declarations for remaining styles not covered by feature level selectors. $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); + // Store custom CSS for the style variation. + if ( isset( $style_variation_node['css'] ) ) { + $style_variation_custom_css[ $style_variation['selector'] ] = $this->process_blocks_custom_css( $style_variation_node['css'], $style_variation['selector'] ); + } } } @@ -2745,7 +2844,7 @@ static function ( $pseudo_selector ) use ( $selector ) { } // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( ":where($selector)", $declarations ); + $block_rules .= static::to_ruleset( ":root :where($selector)", $declarations ); // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { @@ -2762,12 +2861,20 @@ static function ( $pseudo_selector ) use ( $selector ) { // 5. Generate and append the feature level rulesets. foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) { - $block_rules .= static::to_ruleset( ":where($feature_selector)", $individual_feature_declarations ); + $block_rules .= static::to_ruleset( ":root :where($feature_selector)", $individual_feature_declarations ); } // 6. Generate and append the style variation rulesets. foreach ( $style_variation_declarations as $style_variation_selector => $individual_style_variation_declarations ) { - $block_rules .= static::to_ruleset( $style_variation_selector, $individual_style_variation_declarations ); + $block_rules .= static::to_ruleset( ":root :where($style_variation_selector)", $individual_style_variation_declarations ); + if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) { + $block_rules .= $style_variation_custom_css[ $style_variation_selector ]; + } + } + + // 7. Generate and append any custom CSS rules. + if ( isset( $node['css'] ) && ! $is_root_selector ) { + $block_rules .= $this->process_blocks_custom_css( $node['css'], $selector ); } return $block_rules; @@ -2816,16 +2923,12 @@ public function get_root_layout_rules( $selector, $block_metadata ) { $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }'; // Right and left padding are applied to the first container with `.has-global-padding` class. $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; - // Nested containers with `.has-global-padding` class do not get padding. - $css .= '.has-global-padding :where(.has-global-padding:not(.wp-block-block)) { padding-right: 0; padding-left: 0; }'; // Alignfull children of the container with left and right padding have negative margins so they can still be full width. $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; - // The above rule is negated for alignfull children of nested containers. - $css .= '.has-global-padding :where(.has-global-padding:not(.wp-block-block)) > .alignfull { margin-right: 0; margin-left: 0; }'; - // Some of the children of alignfull blocks without content width should also get padding: text blocks and non-alignfull container blocks. - $css .= '.has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; - // The above rule also has to be negated for blocks inside nested `.has-global-padding` blocks. - $css .= '.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }'; + // Nested children of the container with left and right padding that are not wide or full aligned do not get padding. + $css .= '.has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; }'; + // Nested children of the container with left and right padding that are not wide or full aligned do not get negative margin applied. + $css .= '.has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; }'; } $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; @@ -2891,6 +2994,8 @@ protected static function get_metadata_boolean( $data, $path, $default_value = f * * @since 5.8.0 * @since 5.9.0 Duotone preset also has origins. + * @since 6.6.0 Use the spacingScale keyed by origin, and re-generate the + * spacingSizes from spacingScale. * * @param WP_Theme_JSON_Gutenberg $incoming Data to merge. */ @@ -2898,6 +3003,40 @@ public function merge( $incoming ) { $incoming_data = $incoming->get_raw_data(); $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); + /* + * Recompute all the spacing sizes based on the new hierarchy of data. In the constructor + * spacingScale and spacingSizes are both keyed by origin and VALID_ORIGINS is ordered, so + * we can allow partial spacingScale data to inherit missing data from earlier layers when + * computing the spacing sizes. + * + * This happens before the presets are merged to ensure that default spacing sizes can be + * removed from the theme origin if $prevent_override is true. + */ + $flattened_spacing_scale = array(); + foreach ( static::VALID_ORIGINS as $origin ) { + $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); + + // Apply the base spacing scale to the current layer. + $base_spacing_scale = _wp_array_get( $this->theme_json, $scale_path, array() ); + $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale ); + + $spacing_scale = _wp_array_get( $incoming_data, $scale_path, null ); + if ( ! isset( $spacing_scale ) ) { + continue; + } + + // Allow partial scale settings by merging with lower layers. + $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $spacing_scale ); + + // Generate and merge the scales for this layer. + $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); + $spacing_sizes = _wp_array_get( $incoming_data, $sizes_path, array() ); + $spacing_scale_sizes = static::compute_spacing_sizes( $flattened_spacing_scale ); + $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); + + _wp_array_set( $incoming_data, $sizes_path, $merged_spacing_sizes ); + } + /* * The array_replace_recursive algorithm merges at the leaf level, * but we don't want leaf arrays to be merged, so we overwrite it. @@ -3161,14 +3300,21 @@ protected static function filter_slugs( $node, $slugs ) { * Removes insecure data from theme.json. * * @since 5.9.0 + * @since 6.6.0 Added support for block style variation element styles and $origin parameter. * * @param array $theme_json Structure to sanitize. + * @param string $origin Optional. What source of data this object represents. + * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. * @return array Sanitized structure. */ - public static function remove_insecure_properties( $theme_json ) { + public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) { + if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { + $origin = 'theme'; + } + $sanitized = array(); - $theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json ); + $theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin ); $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); @@ -3222,6 +3368,29 @@ public static function remove_insecure_properties( $theme_json ) { } $variation_output = static::remove_insecure_styles( $variation_input ); + + // Process a variation's elements and element pseudo selector styles. + if ( isset( $variation_input['elements'] ) ) { + foreach ( $valid_element_names as $element_name ) { + $element_input = $variation_input['elements'][ $element_name ] ?? null; + if ( $element_input ) { + $element_output = static::remove_insecure_styles( $element_input ); + + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { + if ( isset( $element_input[ $pseudo_selector ] ) ) { + $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); + } + } + } + + if ( ! empty( $element_output ) ) { + _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output ); + } + } + } + } + if ( ! empty( $variation_output ) ) { _wp_array_set( $sanitized, $variation['path'], $variation_output ); } @@ -3418,53 +3587,32 @@ public static function get_from_editor_settings( $settings ) { // Deprecated theme supports. if ( isset( $settings['disableCustomColors'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; } if ( isset( $settings['disableCustomGradients'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; } if ( isset( $settings['disableCustomFontSizes'] ) ) { - if ( ! isset( $theme_settings['settings']['typography'] ) ) { - $theme_settings['settings']['typography'] = array(); - } $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; } if ( isset( $settings['enableCustomLineHeight'] ) ) { - if ( ! isset( $theme_settings['settings']['typography'] ) ) { - $theme_settings['settings']['typography'] = array(); - } $theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight']; } if ( isset( $settings['enableCustomUnits'] ) ) { - if ( ! isset( $theme_settings['settings']['spacing'] ) ) { - $theme_settings['settings']['spacing'] = array(); - } $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) : $settings['enableCustomUnits']; } if ( isset( $settings['colors'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['palette'] = $settings['colors']; } if ( isset( $settings['gradients'] ) ) { - if ( ! isset( $theme_settings['settings']['color'] ) ) { - $theme_settings['settings']['color'] = array(); - } $theme_settings['settings']['color']['gradients'] = $settings['gradients']; } @@ -3476,19 +3624,17 @@ public static function get_from_editor_settings( $settings ) { $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; } } - if ( ! isset( $theme_settings['settings']['typography'] ) ) { - $theme_settings['settings']['typography'] = array(); - } $theme_settings['settings']['typography']['fontSizes'] = $font_sizes; } if ( isset( $settings['enableCustomSpacing'] ) ) { - if ( ! isset( $theme_settings['settings']['spacing'] ) ) { - $theme_settings['settings']['spacing'] = array(); - } $theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing']; } + if ( isset( $settings['spacingSizes'] ) ) { + $theme_settings['settings']['spacing']['spacingSizes'] = $settings['spacingSizes']; + } + return $theme_settings; } @@ -3654,11 +3800,16 @@ public function get_data() { * Sets the spacingSizes array based on the spacingScale values from theme.json. * * @since 6.1.0 + * @deprecated 6.6.0 No longer used as the spacingSizes are automatically + * generated in the constructor and merge methods instead + * of manually after instantiation. * * @return null|void */ public function set_spacing_sizes() { - $spacing_scale = $this->theme_json['settings']['spacing']['spacingScale'] ?? array(); + _deprecated_function( __METHOD__, '6.6.0' ); + + $spacing_scale = $this->theme_json['settings']['spacing']['spacingScale']['default'] ?? array(); // Gutenberg didn't have the 1st isset check. if ( ! isset( $spacing_scale['steps'] ) @@ -3682,6 +3833,99 @@ public function set_spacing_sizes() { return null; } + $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); + + // If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes. + if ( $spacing_scale['steps'] <= 7 ) { + for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { + $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); + } + } + + _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); + } + + /** + * Merges two sets of spacing size presets. + * + * @since 6.6.0 + * + * @param array $base The base set of spacing sizes. + * @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values. + * @return array The merged set of spacing sizes. + */ + private static function merge_spacing_sizes( $base, $incoming ) { + // Preserve the order if there are no base (spacingScale) values. + if ( empty( $base ) ) { + return $incoming; + } + $merged = array(); + foreach ( $base as $item ) { + $merged[ $item['slug'] ] = $item; + } + foreach ( $incoming as $item ) { + $merged[ $item['slug'] ] = $item; + } + ksort( $merged, SORT_NUMERIC ); + return array_values( $merged ); + } + + /** + * Generates a set of spacing sizes by starting with a medium size and + * applying an operator with an increment value to generate the rest of the + * sizes outward from the medium size. The medium slug is '50' with the rest + * of the slugs being 10 apart. The generated names use t-shirt sizing. + * + * Example: + * + * $spacing_scale = array( + * 'steps' => 4, + * 'mediumStep' => 16, + * 'unit' => 'px', + * 'operator' => '+', + * 'increment' => 2, + * ); + * $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); + * // -> array( + * // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ), + * // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ), + * // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ), + * // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ), + * // ) + * + * @since 6.6.0 + * + * @param array $spacing_scale { + * The spacing scale values. All are required. + * + * @type int $steps The number of steps in the scale. (up to 10 steps are supported.) + * @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.) + * @type string $unit The CSS unit to use for the sizes. + * @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'. + * @type float $increment The value used with the operator to generate the other sizes. + * } + * @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid. + */ + private static function compute_spacing_sizes( $spacing_scale ) { + /* + * This condition is intentionally missing some checks on ranges for the values in order to + * keep backwards compatibility with the previous implementation. + */ + if ( + ! isset( $spacing_scale['steps'] ) || + ! is_numeric( $spacing_scale['steps'] ) || + 0 === $spacing_scale['steps'] || + ! isset( $spacing_scale['mediumStep'] ) || + ! is_numeric( $spacing_scale['mediumStep'] ) || + ! isset( $spacing_scale['unit'] ) || + ! isset( $spacing_scale['operator'] ) || + ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) || + ! isset( $spacing_scale['increment'] ) || + ! is_numeric( $spacing_scale['increment'] ) + ) { + return array(); + } + $unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] ); $current_step = $spacing_scale['mediumStep']; $steps_mid_point = round( $spacing_scale['steps'] / 2, 0 ); @@ -3764,14 +4008,7 @@ public function set_spacing_sizes() { $spacing_sizes[] = $above_sizes_item; } - // If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes. - if ( $spacing_scale['steps'] <= 7 ) { - for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { - $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); - } - } - - _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); + return $spacing_sizes; } /** diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index dcc0bf8b099c3b..507876af4952a8 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -173,8 +173,7 @@ public static function get_core_data() { * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data_Gutenberg( $config, 'default' ) ); - $config = $theme_json->get_data(); - static::$core = new WP_Theme_JSON_Gutenberg( $config, 'default' ); + static::$core = $theme_json->get_theme_json(); return static::$core; } @@ -221,6 +220,7 @@ protected static function has_same_registered_blocks( $origin ) { * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. + * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. * * @param array $deprecated Deprecated. Not used. * @param array $options { @@ -254,9 +254,8 @@ public static function get_theme_data( $deprecated = array(), $options = array() * * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data. */ - $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data_Gutenberg( $theme_json_data, 'theme' ) ); - $theme_json_data = $theme_json->get_data(); - static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data ); + $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data_Gutenberg( $theme_json_data, 'theme' ) ); + static::$theme = $theme_json->get_theme_json(); if ( $wp_theme->parent() ) { // Get parent theme.json. @@ -293,35 +292,27 @@ public static function get_theme_data( $deprecated = array(), $options = array() * So we take theme supports, transform it to theme.json shape * and merge the static::$theme upon that. */ - $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); + $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); if ( ! wp_theme_has_theme_json() ) { - if ( ! isset( $theme_support_data['settings']['color'] ) ) { - $theme_support_data['settings']['color'] = array(); - } - - $default_palette = false; - if ( current_theme_supports( 'default-color-palette' ) ) { - $default_palette = true; - } - if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { - // If the theme does not have any palette, we still want to show the core one. - $default_palette = true; - } - $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; - - $default_gradients = false; - if ( current_theme_supports( 'default-gradient-presets' ) ) { - $default_gradients = true; - } - if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { - // If the theme does not have any gradients, we still want to show the core ones. - $default_gradients = true; - } - $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; + /* + * Unlike block themes, classic themes without a theme.json disable + * default presets when custom preset theme support is added. This + * behavior can be overridden by using the corresponding default + * preset theme support. + */ + $theme_support_data['settings']['color']['defaultPalette'] = + ! isset( $theme_support_data['settings']['color']['palette'] ) || + current_theme_supports( 'default-color-palette' ); + $theme_support_data['settings']['color']['defaultGradients'] = + ! isset( $theme_support_data['settings']['color']['gradients'] ) || + current_theme_supports( 'default-gradient-presets' ); + $theme_support_data['settings']['typography']['defaultFontSizes'] = + ! isset( $theme_support_data['settings']['typography']['fontSizes'] ) || + current_theme_supports( 'default-font-sizes' ); + $theme_support_data['settings']['spacing']['defaultSpacingSizes'] = + ! isset( $theme_support_data['settings']['spacing']['spacingSizes'] ) || + current_theme_supports( 'default-spacing-sizes' ); - if ( ! isset( $theme_support_data['settings']['shadow'] ) ) { - $theme_support_data['settings']['shadow'] = array(); - } /* * Shadow presets are explicitly disabled for classic themes until a * decision is made for whether the default presets should match the @@ -398,10 +389,9 @@ public static function get_block_data() { * * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data. */ - $theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data_Gutenberg( $config, 'blocks' ) ); - $config = $theme_json->get_data(); + $theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data_Gutenberg( $config, 'blocks' ) ); + static::$blocks = $theme_json->get_theme_json(); - static::$blocks = new WP_Theme_JSON_Gutenberg( $config, 'blocks' ); return static::$blocks; } @@ -533,8 +523,8 @@ public static function get_user_data() { * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); - $config = $theme_json->get_data(); - return new WP_Theme_JSON_Gutenberg( $config, 'custom' ); + + return $theme_json->get_theme_json(); } // Very important to verify that the flag isGlobalStylesUserThemeJSON is true. @@ -544,18 +534,14 @@ 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(); - - // Needs to be set for schema migrations of user data. - $config['isGlobalStylesUserThemeJSON'] = true; - - static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' ); + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); + static::$user = $theme_json->get_theme_json(); return static::$user; } @@ -603,7 +589,6 @@ public static function get_merged_data( $origin = 'custom' ) { $result = new WP_Theme_JSON_Gutenberg(); $result->merge( static::get_core_data() ); if ( 'default' === $origin ) { - $result->set_spacing_sizes(); return $result; } @@ -614,12 +599,10 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { - $result->set_spacing_sizes(); return $result; } $result->merge( static::get_user_data() ); - $result->set_spacing_sizes(); return $result; } @@ -719,14 +702,44 @@ private static function recursively_iterate_json( $dir ) { return $nested_json_files; } + /** + * Determines if a supplied style variation matches the provided scope. + * + * For backwards compatibility, if a variation does not define any scope + * related property, e.g. `blockTypes`, it is assumed to be a theme style + * variation. + * + * @since 6.6.0 + * + * @param array $variation Theme.json shaped style variation object. + * @param string $scope Scope to check e.g. theme, block etc. + * + * @return boolean + */ + private static function style_variation_has_scope( $variation, $scope ) { + if ( 'block' === $scope ) { + return isset( $variation['blockTypes'] ); + } + + if ( 'theme' === $scope ) { + return ! isset( $variation['blockTypes'] ); + } + + return false; + } + /** * Returns the style variations defined by the theme (parent and child). * * @since 6.2.0 Returns parent theme variations if theme is a child. + * @since 6.6.0 Added configurable scope parameter to allow filtering + * theme.json partial files by the scope to which they + * can be applied e.g. theme vs block etc. * + * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. * @return array */ - public static function get_style_variations() { + public static function get_style_variations( $scope = 'theme' ) { $variation_files = array(); $variations = array(); $base_directory = get_stylesheet_directory() . '/styles'; @@ -749,7 +762,7 @@ public static function get_style_variations() { ksort( $variation_files ); foreach ( $variation_files as $path => $file ) { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); - if ( is_array( $decoded_file ) ) { + if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); if ( empty( $variation['title'] ) ) { @@ -760,4 +773,78 @@ public static function get_style_variations() { } return $variations; } + + + /** + * Resolves relative paths in theme.json styles to theme absolute paths + * and returns them in an array that can be embedded + * as the value of `_link` object in REST API responses. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @return array An array of resolved paths. + */ + public static function get_resolved_theme_uris( $theme_json ) { + $resolved_theme_uris = array(); + + if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { + return $resolved_theme_uris; + } + + $theme_json_data = $theme_json->get_raw_data(); + + // Top level styles. + $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; + // Using the same file convention when registering web fonts. See: WP_Font_Face_Resolver:: to_theme_file_uri. + $placeholder = 'file:./'; + if ( + isset( $background_image_url ) && + is_string( $background_image_url ) && + // Skip if the src doesn't start with the placeholder, as there's nothing to replace. + str_starts_with( $background_image_url, $placeholder ) ) { + $file_type = wp_check_filetype( $background_image_url ); + $src_url = str_replace( $placeholder, '', $background_image_url ); + $resolved_theme_uri = array( + 'name' => $background_image_url, + 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), + 'target' => 'styles.background.backgroundImage.url', + ); + if ( isset( $file_type['type'] ) ) { + $resolved_theme_uri['type'] = $file_type['type']; + } + $resolved_theme_uris[] = $resolved_theme_uri; + } + + return $resolved_theme_uris; + } + + /** + * Resolves relative paths in theme.json styles to theme absolute paths + * and merges them with incoming theme JSON. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @return WP_Theme_JSON_Gutenberg Theme merged with resolved paths, if any found. + */ + public static function resolve_theme_file_uris( $theme_json ) { + $resolved_urls = static::get_resolved_theme_uris( $theme_json ); + if ( empty( $resolved_urls ) ) { + return $theme_json; + } + + $resolved_theme_json_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + ); + + foreach ( $resolved_urls as $resolved_url ) { + $path = explode( '.', $resolved_url['target'] ); + _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); + } + + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $resolved_theme_json_data ) ); + + return $theme_json; + } } diff --git a/lib/class-wp-theme-json-schema-gutenberg.php b/lib/class-wp-theme-json-schema-gutenberg.php index 1eea7ddaa27368..af0977ada1b307 100644 --- a/lib/class-wp-theme-json-schema-gutenberg.php +++ b/lib/class-wp-theme-json-schema-gutenberg.php @@ -40,11 +40,13 @@ class WP_Theme_JSON_Schema_Gutenberg { * @since 5.9.0 * @since 6.6.0 Migrate up to v3. * - * @param array $theme_json The structure to migrate. + * @param array $theme_json The structure to migrate. + * @param string $origin Optional. What source of data this object represents. + * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. * * @return array The structure in the last version. */ - public static function migrate( $theme_json ) { + public static function migrate( $theme_json, $origin = 'theme' ) { if ( ! isset( $theme_json['version'] ) ) { $theme_json = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA, @@ -55,10 +57,9 @@ public static function migrate( $theme_json ) { switch ( $theme_json['version'] ) { case 1: $theme_json = self::migrate_v1_to_v2( $theme_json ); - // no break + // Deliberate fall through. Once migrated to v2, also migrate to v3. case 2: - $theme_json = self::migrate_v2_to_v3( $theme_json ); - // no break + $theme_json = self::migrate_v2_to_v3( $theme_json, $origin ); } return $theme_json; @@ -97,15 +98,19 @@ private static function migrate_v1_to_v2( $old ) { /** * Migrates from v2 to v3. * - * - Sets settings.typography.defaultFontSizes to false. + * - Sets settings.typography.defaultFontSizes to false if settings.typography.fontSizes are defined. + * - Sets settings.spacing.defaultSpacingSizes to false if settings.spacing.spacingSizes are defined. + * - Prevents settings.spacing.spacingSizes from merging with settings.spacing.spacingScale by + * unsetting spacingScale when spacingSizes are defined. * * @since 6.6.0 * - * @param array $old Data to migrate. - * + * @param array $old Data to migrate. + * @param string $origin What source of data this object represents. + * One of 'blocks', 'default', 'theme', or 'custom'. * @return array Data with defaultFontSizes set to false. */ - private static function migrate_v2_to_v3( $old ) { + private static function migrate_v2_to_v3( $old, $origin ) { // Copy everything. $new = $old; @@ -116,10 +121,7 @@ private static function migrate_v2_to_v3( $old ) { * 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'] - ) { + if ( 'custom' === $origin ) { return $new; } @@ -131,16 +133,36 @@ private static function migrate_v2_to_v3( $old ) { * 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(); - } + if ( isset( $old['settings']['typography']['fontSizes'] ) ) { $new['settings']['typography']['defaultFontSizes'] = false; } + /* + * Similarly to defaultFontSizes, we need to migrate defaultSpacingSizes + * as it controls the PRESETS_METADATA prevent_override which was + * previously hardcoded to false. This only needs to happen when the + * theme provided spacing sizes via spacingSizes or spacingScale. + */ + if ( + isset( $old['settings']['spacing']['spacingSizes'] ) || + isset( $old['settings']['spacing']['spacingScale'] ) + ) { + $new['settings']['spacing']['defaultSpacingSizes'] = false; + } + + /* + * In v3 spacingSizes is merged with the generated spacingScale sizes + * instead of completely replacing them. The v3 behavior is what was + * documented for the v2 schema, but the code never actually did work + * that way. Instead of surprising users with a behavior change two + * years after the fact at the same time as a v3 update is introduced, + * we'll continue using the "bugged" behavior for v2 themes. And treat + * the "bug fix" as a breaking change for v3. + */ + if ( isset( $old['settings']['spacing']['spacingSizes'] ) ) { + unset( $new['settings']['spacing']['spacingScale'] ); + } + return $new; } diff --git a/lib/client-assets.php b/lib/client-assets.php index 13884f90fb3ea5..a159bc53e6a591 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -590,6 +590,14 @@ function gutenberg_register_vendor_scripts( $scripts ) { array( 'react' ), '18' ); + + gutenberg_override_script( + $scripts, + 'react-jsx-runtime', + gutenberg_url( 'build/vendors/react-jsx-runtime' . $extension ), + array( 'react' ), + '18' + ); } add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' ); diff --git a/lib/compat/wordpress-6.4/script-loader.php b/lib/compat/wordpress-6.4/script-loader.php index 373bb9e90f8583..60de7785aaeb22 100644 --- a/lib/compat/wordpress-6.4/script-loader.php +++ b/lib/compat/wordpress-6.4/script-loader.php @@ -11,6 +11,9 @@ * @since 6.0.0 * @access private * + * @global WP_Styles $wp_styles + * @global WP_Scripts $wp_scripts + * * @return array { * The block editor assets. * diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php index d3a8b3f5571813..b91e89103faa02 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -46,135 +46,179 @@ function gutenberg_register_metadata_attribute( $args ) { } add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' ); -/** - * Depending on the block attribute name, replace its value in the HTML based on the value provided. - * - * @param string $block_content Block Content. - * @param string $block_name The name of the block to process. - * @param string $attribute_name The attribute name to replace. - * @param mixed $source_value The value used to replace in the HTML. - * @return string The modified block content. - */ -function gutenberg_block_bindings_replace_html( $block_content, $block_name, string $attribute_name, $source_value ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); - if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { - return $block_content; - } +// Only process block bindings if they are not processed in core. +if ( ! class_exists( 'WP_Block_Bindings_Registry' ) ) { + /** + * Depending on the block attribute name, replace its value in the HTML based on the value provided. + * + * @param string $block_content Block Content. + * @param string $block_name The name of the block to process. + * @param string $attribute_name The attribute name to replace. + * @param mixed $source_value The value used to replace in the HTML. + * @return string The modified block content. + */ + function gutenberg_block_bindings_replace_html( $block_content, $block_name, string $attribute_name, $source_value ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { + return $block_content; + } - // Depending on the attribute source, the processing will be different. - switch ( $block_type->attributes[ $attribute_name ]['source'] ) { - case 'html': - case 'rich-text': - // Hardcode the selectors and processing until the HTML API is able to read CSS selectors and replace inner HTML. - // TODO: Use the HTML API instead. - if ( 'core/paragraph' === $block_name && 'content' === $attribute_name ) { - $selector = 'p'; - } - if ( 'core/heading' === $block_name && 'content' === $attribute_name ) { - $selector = 'h[1-6]'; - } - if ( 'core/button' === $block_name && 'text' === $attribute_name ) { - // Check if it is a , + , + ] } + > + { blockType?.title }: + { __( 'This block can only be used once.' ) } + + ); +} diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 6da035a883ffb1..e01cd095752696 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -111,10 +111,8 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { // displays based on the relationship between the selected block // and its parent, and only enable it if the parent is controlling // its children blocks. - const blockInspectorAnimationSettings = useBlockInspectorAnimationSettings( - blockType, - selectedBlockClientId - ); + const blockInspectorAnimationSettings = + useBlockInspectorAnimationSettings( blockType ); const borderPanelLabel = useBorderPanelLabel( { blockName: selectedBlockName, diff --git a/packages/block-editor/src/components/block-inspector/useBlockInspectorAnimationSettings.js b/packages/block-editor/src/components/block-inspector/useBlockInspectorAnimationSettings.js index 6be7d3fe18667e..d89639c4b9ac8c 100644 --- a/packages/block-editor/src/components/block-inspector/useBlockInspectorAnimationSettings.js +++ b/packages/block-editor/src/components/block-inspector/useBlockInspectorAnimationSettings.js @@ -8,10 +8,7 @@ import { useSelect } from '@wordpress/data'; */ import { store as blockEditorStore } from '../../store'; -export default function useBlockInspectorAnimationSettings( - blockType, - selectedBlockClientId -) { +export default function useBlockInspectorAnimationSettings( blockType ) { return useSelect( ( select ) => { if ( blockType ) { @@ -48,6 +45,6 @@ export default function useBlockInspectorAnimationSettings( } return null; }, - [ selectedBlockClientId, blockType ] + [ blockType ] ); } diff --git a/packages/block-editor/src/components/block-list/block-invalid-warning.native.js b/packages/block-editor/src/components/block-list/block-invalid-warning.native.js index d27b2e436f2961..8734f6f8804f03 100644 --- a/packages/block-editor/src/components/block-list/block-invalid-warning.native.js +++ b/packages/block-editor/src/components/block-list/block-invalid-warning.native.js @@ -54,7 +54,7 @@ export default function BlockInvalidWarning( { clientId } ) { { __unstableMarkLastChangeAsPersistent, moveBlocksToPosition, removeBlock, + selectBlock, } = dispatch( blockEditorStore ); // Do not add new properties here, use `useDispatch` instead to avoid @@ -303,6 +307,28 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { canInsertBlockType, } = registry.select( blockEditorStore ); + function switchToDefaultOrRemove() { + const block = getBlock( clientId ); + const defaultBlockName = getDefaultBlockName(); + if ( getBlockName( clientId ) !== defaultBlockName ) { + const replacement = switchToBlockType( + block, + defaultBlockName + ); + if ( replacement && replacement.length ) { + replaceBlocks( clientId, replacement ); + } + } else if ( isUnmodifiedDefaultBlock( block ) ) { + const nextBlockClientId = getNextBlockClientId( clientId ); + if ( nextBlockClientId ) { + registry.batch( () => { + removeBlock( clientId ); + selectBlock( nextBlockClientId ); + } ); + } + } + } + /** * Moves the block with clientId up one level. If the block type * cannot be inserted at the new location, it will be attempted to @@ -342,7 +368,16 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { getDefaultBlockName() ); - if ( replacement && replacement.length ) { + if ( + replacement && + replacement.length && + replacement.every( ( block ) => + canInsertBlockType( + block.name, + targetRootClientId + ) + ) + ) { insertBlocks( replacement, getBlockIndex( _clientId ), @@ -350,6 +385,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { changeSelection ); removeBlock( firstClientId, false ); + } else { + switchToDefaultOrRemove(); } } @@ -460,16 +497,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { } moveFirstItemUp( rootClientId ); - } else if ( - getBlockName( clientId ) !== getDefaultBlockName() - ) { - const replacement = switchToBlockType( - getBlock( clientId ), - getDefaultBlockName() - ); - if ( replacement && replacement.length ) { - replaceBlocks( clientId, replacement ); - } + } else { + switchToDefaultOrRemove(); } } }, @@ -532,6 +561,7 @@ function BlockListBlockProvider( props ) { isFirstMultiSelectedBlock, getMultiSelectedBlockClientIds, hasSelectedInnerBlock, + getBlocksByName, getBlockIndex, isBlockMultiSelected, @@ -593,8 +623,8 @@ function BlockListBlockProvider( props ) { } const _isSelected = isBlockSelected( clientId ); - const canRemove = canRemoveBlock( clientId, rootClientId ); - const canMove = canMoveBlock( clientId, rootClientId ); + const canRemove = canRemoveBlock( clientId ); + const canMove = canMoveBlock( clientId ); const match = getActiveBlockVariation( blockName, attributes ); const isMultiSelected = isBlockMultiSelected( clientId ); const checkDeep = true; @@ -605,6 +635,17 @@ function BlockListBlockProvider( props ) { const movingClientId = hasBlockMovingClientId(); const blockEditingMode = getBlockEditingMode( clientId ); + const multiple = hasBlockSupport( blockName, 'multiple', true ); + + // For block types with `multiple` support, there is no "original + // block" to be found in the content, as the block itself is valid. + const blocksWithSameName = multiple + ? [] + : getBlocksByName( blockName ); + const isInvalid = + blocksWithSameName.length && + blocksWithSameName[ 0 ] !== clientId; + return { ...previewContext, mode: getBlockMode( clientId ), @@ -662,6 +703,9 @@ function BlockListBlockProvider( props ) { hasEditableOutline: blockEditingMode !== 'disabled' && getBlockEditingMode( rootClientId ) === 'disabled', + originalBlockClientId: isInvalid + ? blocksWithSameName[ 0 ] + : false, }; }, [ clientId, rootClientId ] @@ -705,6 +749,7 @@ function BlockListBlockProvider( props ) { hasEditableOutline, className, defaultClassName, + originalBlockClientId, } = selectedProps; // Users of the editor.BlockListBlock filter used to be able to @@ -752,6 +797,7 @@ function BlockListBlockProvider( props ) { defaultClassName, mayDisplayControls, mayDisplayParentControls, + originalBlockClientId, themeSupportsLayout, }; diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 03456c9fb77aed..4112d614da35fc 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -432,8 +432,8 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => { const attributes = getBlockAttributes( clientId ); const isSelected = isBlockSelected( clientId ); const templateLock = getTemplateLock( rootClientId ); - const canRemove = canRemoveBlock( clientId, rootClientId ); - const canMove = canMoveBlock( clientId, rootClientId ); + const canRemove = canRemoveBlock( clientId ); + const canMove = canMoveBlock( clientId ); // The fallback to `{}` is a temporary fix. // This function should never be called when a block is not present in @@ -669,8 +669,16 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { } moveFirstItemUp( rootClientId ); - } else { - removeBlock( clientId ); + } else if ( + getBlockName( clientId ) !== getDefaultBlockName() + ) { + const replacement = switchToBlockType( + getBlock( clientId ), + getDefaultBlockName() + ); + if ( replacement && replacement.length ) { + replaceBlocks( clientId, replacement ); + } } } }, diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js index 044e5b185a2244..68fa8628c54c72 100644 --- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js +++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js @@ -40,7 +40,9 @@ export function useInBetweenInserter() { } function onMouseMove( event ) { - if ( openRef.current ) { + // openRef is the reference to the insertion point between blocks. + // If the reference is not set or the insertion point is already open, return. + if ( openRef === undefined || openRef.current ) { return; } diff --git a/packages/block-editor/src/components/block-lock/use-block-lock.js b/packages/block-editor/src/components/block-lock/use-block-lock.js index da6ccc4092096d..9917a739eacd83 100644 --- a/packages/block-editor/src/components/block-lock/use-block-lock.js +++ b/packages/block-editor/src/components/block-lock/use-block-lock.js @@ -24,14 +24,12 @@ export default function useBlockLock( clientId ) { canRemoveBlock, canLockBlockType, getBlockName, - getBlockRootClientId, getTemplateLock, } = select( blockEditorStore ); - const rootClientId = getBlockRootClientId( clientId ); const canEdit = canEditBlock( clientId ); - const canMove = canMoveBlock( clientId, rootClientId ); - const canRemove = canRemoveBlock( clientId, rootClientId ); + const canMove = canMoveBlock( clientId ); + const canRemove = canRemoveBlock( clientId ); return { canEdit, diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index a143ab391a43fd..24f259613351d2 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -46,7 +46,7 @@ function BlockMover( { const blockOrder = getBlockOrder( _rootClientId ); return { - canMove: canMoveBlocks( clientIds, _rootClientId ), + canMove: canMoveBlocks( clientIds ), rootClientId: _rootClientId, isFirst: firstIndex === 0, isLast: lastIndex === blockOrder.length - 1, diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 326479cd3bc6e5..74a72aec01dbef 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -166,7 +166,7 @@ export default compose( numberOfBlocks: blockOrder.length - 1, isFirst: firstIndex === 0, isLast: lastIndex === blockOrder.length - 1, - canMove: canMoveBlocks( clientIds, rootClientId ), + canMove: canMoveBlocks( clientIds ), rootClientId, }; } ), diff --git a/packages/block-editor/src/components/block-patterns-list/index.js b/packages/block-editor/src/components/block-patterns-list/index.js index 845bfc4f803a35..b2af3456be7b0c 100644 --- a/packages/block-editor/src/components/block-patterns-list/index.js +++ b/packages/block-editor/src/components/block-patterns-list/index.js @@ -147,7 +147,10 @@ function BlockPattern( { /> { showTitle && ( - + { pattern.type === INSERTER_PATTERN_TYPES.user && ! pattern.syncStatus && ( diff --git a/packages/block-editor/src/components/block-patterns-list/style.scss b/packages/block-editor/src/components/block-patterns-list/style.scss index d3c54a20956f39..6b23c1e844dad9 100644 --- a/packages/block-editor/src/components/block-patterns-list/style.scss +++ b/packages/block-editor/src/components/block-patterns-list/style.scss @@ -1,6 +1,6 @@ .block-editor-block-patterns-list__list-item { cursor: pointer; - margin-bottom: $grid-unit-30; + margin-bottom: $grid-unit-20; // The list item contains absolutely positioned visually hidden text, // so make this container relative. This prevents the bug experienced in @@ -26,38 +26,41 @@ // the bottom margin set on `...__list-item` above scroll-margin-bottom: ($grid-unit-40 + $grid-unit-30); + .block-editor-block-patterns-list__item-title { + flex-grow: 1; + font-size: $helptext-font-size; + text-align: left; + } + .block-editor-block-preview__container { display: flex; align-items: center; overflow: hidden; - border-radius: 4px; - } + border-radius: $radius-block-ui; - .block-editor-block-patterns-list__item-title { - text-align: left; - flex-grow: 1; - } - - &:hover .block-editor-block-preview__container { - box-shadow: 0 0 0 2px $gray-900; + &::after { + outline: $border-width solid rgba($black, 0.1); + outline-offset: -$border-width; + border-radius: $radius-block-ui; + } } - &:focus .block-editor-block-preview__container { - @include button-style-outset__focus($gray-900); + &:hover:not(:focus) .block-editor-block-preview__container::after { + outline-color: rgba($black, 0.3); } - &.block-editor-block-patterns-list__list-item-synced { - &:hover, - &:focus { - .block-editor-block-preview__container { - box-shadow: 0 0 0 2px var(--wp-block-synced-color); - } - } + &:focus .block-editor-block-preview__container::after { + outline-color: var(--wp-admin-theme-color); + outline-width: var(--wp-admin-border-width-focus); + outline-offset: calc((-1 * var(--wp-admin-border-width-focus))); + transition: outline 0.1s linear; + @include reduce-motion("transition"); } - .block-editor-patterns__pattern-details { + .block-editor-patterns__pattern-details:not(:empty) { align-items: center; margin-top: $grid-unit-10; + padding-bottom: $grid-unit-05; // Add more space for labels on user-created patterns. } .block-editor-patterns__pattern-icon-wrapper { diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index bfc3d5c8783beb..9bdd85f66445f8 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -48,29 +48,3 @@ bottom: 0; z-index: 1; } - -// Restrict these shadows to the context of the inserter. -.editor-inserter-sidebar { - .block-editor-block-patterns-list__item { - .block-editor-block-preview__container { - box-shadow: 0 15px 25px rgb(0 0 0 / 7%); - } - &:focus, - &:hover { - .block-editor-block-preview__container { - box-shadow: 0 0 0 2px $gray-900, 0 15px 25px rgb(0 0 0 / 7%); - } - } - - &.block-editor-block-patterns-list__list-item-synced { - &:hover, - &:focus { - .block-editor-block-preview__container { - box-shadow: - 0 0 0 2px var(--wp-block-synced-color), - 0 15px 25px rgb(0 0 0 / 7%); - } - } - } - } -} diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index 62c5ff4a5f158c..069bd2c9ff1ee4 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -256,7 +256,7 @@ export function BlockSettingsDropdown( { { __( 'Duplicate' ) } ) } - { canInsertBlock && ( + { canInsertBlock && ! isContentOnly && ( <> { - const { - getBlockRootClientId, - getBlockAttributes, - canRemoveBlocks, - } = select( blockEditorStore ); + const { getBlockAttributes, canRemoveBlocks } = + select( blockEditorStore ); const { getActiveBlockVariation, getBlockVariations } = select( blocksStore ); - const rootClientId = getBlockRootClientId( - Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds - ); - const canRemove = canRemoveBlocks( clientIds, rootClientId ); + + const canRemove = canRemoveBlocks( clientIds ); // Only handle single selected blocks for now. if ( blocks.length !== 1 || ! canRemove ) { return EMPTY_OBJECT; diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 8b01907474d23a..f7f75dd8fa2ccb 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -1,12 +1,14 @@ /** * WordPress dependencies */ -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf, _x } from '@wordpress/i18n'; import { DropdownMenu, ToolbarButton, ToolbarGroup, ToolbarItem, + __experimentalText as Text, + MenuGroup, } from '@wordpress/components'; import { switchToBlockType, @@ -33,6 +35,7 @@ function BlockSwitcherDropdownMenuContents( { clientIds, hasBlockStyles, canRemove, + isUsingBindings, } ) { const { replaceBlocks, multiSelect, updateBlockAttributes } = useDispatch( blockEditorStore ); @@ -118,6 +121,17 @@ function BlockSwitcherDropdownMenuContents( {

); } + + const connectedBlockDescription = isSingleBlock + ? _x( + 'This block is connected.', + 'block toolbar button label and description' + ) + : _x( + 'These blocks are connected.', + 'block toolbar button label and description' + ); + return (
{ hasPatternTransformation && ( @@ -156,11 +170,18 @@ function BlockSwitcherDropdownMenuContents( { onSwitch={ onClose } /> ) } + { isUsingBindings && ( + + + { connectedBlockDescription } + + + ) }
); } -export const BlockSwitcher = ( { clientIds, disabled } ) => { +export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { const { canRemove, hasBlockStyles, @@ -170,21 +191,14 @@ export const BlockSwitcher = ( { clientIds, disabled } ) => { isTemplate, } = useSelect( ( select ) => { - const { - getBlockRootClientId, - getBlocksByClientId, - getBlockAttributes, - canRemoveBlocks, - } = select( blockEditorStore ); + const { getBlocksByClientId, getBlockAttributes, canRemoveBlocks } = + select( blockEditorStore ); const { getBlockStyles, getBlockType, getActiveBlockVariation } = select( blocksStore ); const _blocks = getBlocksByClientId( clientIds ); if ( ! _blocks.length || _blocks.some( ( block ) => ! block ) ) { return { invalidBlocks: true }; } - const rootClientId = getBlockRootClientId( - Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds - ); const [ { name: firstBlockName } ] = _blocks; const _isSingleBlockSelected = _blocks.length === 1; const blockType = getBlockType( firstBlockName ); @@ -206,7 +220,7 @@ export const BlockSwitcher = ( { clientIds, disabled } ) => { } return { - canRemove: canRemoveBlocks( clientIds, rootClientId ), + canRemove: canRemoveBlocks( clientIds ), hasBlockStyles: _isSingleBlockSelected && !! getBlockStyles( firstBlockName )?.length, @@ -303,6 +317,7 @@ export const BlockSwitcher = ( { clientIds, disabled } ) => { clientIds={ clientIds } hasBlockStyles={ hasBlockStyles } canRemove={ canRemove } + isUsingBindings={ isUsingBindings } /> ) } diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 2072616f7fb0f7..5eaba08bf94ae2 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -203,3 +203,8 @@ padding: 6px $grid-unit; margin: 0; } + +.block-editor-block-switcher__binding-indicator { + display: block; + padding: $grid-unit; +} diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index c3570c4a007f16..6c6acd662ed962 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -35,9 +35,8 @@ import { store as blockEditorStore } from '../../store'; import __unstableBlockNameContext from './block-name-context'; import NavigableToolbar from '../navigable-toolbar'; import Shuffle from './shuffle'; -import BlockBindingsIndicator from '../block-bindings-toolbar-indicator'; import { useHasBlockToolbar } from './use-has-block-toolbar'; -import { canBindBlock } from '../../hooks/use-bindings-attributes'; + /** * Renders the block toolbar. * @@ -62,7 +61,6 @@ export function PrivateBlockToolbar( { blockClientIds, isDefaultEditingMode, blockType, - blockName, toolbarKey, shouldShowVisualToolbar, showParentSelector, @@ -94,13 +92,14 @@ export function PrivateBlockToolbar( { const isVisual = selectedBlockClientIds.every( ( id ) => getBlockMode( id ) === 'visual' ); - const _isUsingBindings = !! getBlockAttributes( selectedBlockClientId ) - ?.metadata?.bindings; + const _isUsingBindings = selectedBlockClientIds.every( + ( clientId ) => + !! getBlockAttributes( clientId )?.metadata?.bindings + ); return { blockClientId: selectedBlockClientId, blockClientIds: selectedBlockClientIds, isDefaultEditingMode: _isDefaultEditingMode, - blockName: _blockName, blockType: selectedBlockClientId && getBlockType( _blockName ), shouldShowVisualToolbar: isValid && isVisual, rootClientId: blockRootClientId, @@ -146,6 +145,7 @@ export function PrivateBlockToolbar( { const innerClasses = clsx( 'block-editor-block-toolbar', { 'is-synced': isSynced, + 'is-connected': isUsingBindings, } ); return ( @@ -167,9 +167,6 @@ export function PrivateBlockToolbar( { { ! isMultiToolbar && isLargeViewport && isDefaultEditingMode && } - { isUsingBindings && canBindBlock( blockName ) && ( - - ) } { ( shouldShowVisualToolbar || isMultiToolbar ) && ( isDefaultEditingMode || isSynced ) && (
{ isDefaultEditingMode && ( <> diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 1854e440329647..6314f1a1e7ffd5 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -39,14 +39,17 @@ border-right: $border-width solid $gray-300; } - &.is-synced .block-editor-block-switcher .components-button .block-editor-block-icon { - color: var(--wp-block-synced-color); - } - - &.is-synced .components-toolbar-button.block-editor-block-switcher__no-switcher-icon { - &:disabled .block-editor-block-icon.has-colors { + &.is-synced, + &.is-connected { + .block-editor-block-switcher .components-button .block-editor-block-icon { color: var(--wp-block-synced-color); } + + .components-toolbar-button.block-editor-block-switcher__no-switcher-icon { + &:disabled .block-editor-block-icon.has-colors { + color: var(--wp-block-synced-color); + } + } } > :last-child, diff --git a/packages/block-editor/src/components/block-tools/block-selection-button.js b/packages/block-editor/src/components/block-tools/block-selection-button.js index bed74376b64b18..d4ec0f8cf79fb6 100644 --- a/packages/block-editor/src/components/block-tools/block-selection-button.js +++ b/packages/block-editor/src/components/block-tools/block-selection-button.js @@ -107,8 +107,8 @@ function BlockSelectionButton( { clientId, rootClientId } ) { isBlockTemplatePart, isNextBlockTemplatePart, isPrevBlockTemplatePart, - canRemove: canRemoveBlock( clientId, rootClientId ), - canMove: canMoveBlock( clientId, rootClientId ), + canRemove: canRemoveBlock( clientId ), + canMove: canMoveBlock( clientId ), }; }, [ clientId, rootClientId ] diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index dba0adbbd8325f..ad744a81cca623 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -231,11 +231,12 @@ export default function BlockTools( { name="__unstable-block-tools-after" ref={ blockToolbarAfterRef } /> - { isZoomOutMode && ( - - ) } + { window.__experimentalEnableZoomedOutPatternsTab && + isZoomOutMode && ( + + ) }
); diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index e77654619255b9..84087b262786c4 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -182,8 +182,8 @@ .block-editor-block-list__block-selection-button, .block-editor-block-contextual-toolbar { pointer-events: all; - margin-top: $grid-unit-15; - margin-bottom: $grid-unit-15; + margin-top: $grid-unit-10; + margin-bottom: $grid-unit-10; } .block-editor-block-contextual-toolbar { diff --git a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js index 4396f54b19707b..4f718a552c5541 100644 --- a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js +++ b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js @@ -21,6 +21,7 @@ function ZoomOutModeInserters() { sectionRootClientId, insertionPoint, setInserterIsOpened, + selectedSection, } = useSelect( ( select ) => { const { getSettings, getBlockOrder } = select( blockEditorStore ); const { sectionRootClientId: root } = unlock( getSettings() ); @@ -32,6 +33,7 @@ function ZoomOutModeInserters() { // eslint-disable-next-line @wordpress/data-no-store-string-literals const editor = select( 'core/editor' ); return { + selectedSection: editor.getSelectedBlock(), blockOrder: getBlockOrder( root ), insertionPoint: unlock( editor ).getInsertionPoint(), sectionRootClientId: root, @@ -49,8 +51,7 @@ function ZoomOutModeInserters() { } // reset insertion point when the block order changes setInserterIsOpened( true ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ blockOrder ] ); + }, [ blockOrder, setInserterIsOpened ] ); // Defer the initial rendering to avoid the jumps due to the animation. useEffect( () => { @@ -62,7 +63,7 @@ function ZoomOutModeInserters() { }; }, [] ); - if ( ! isReady ) { + if ( ! isReady || ! selectedSection ) { return null; } diff --git a/packages/block-editor/src/components/block-variation-picker/content.scss b/packages/block-editor/src/components/block-variation-picker/content.scss index e4636f288078d3..e7dd3e87cb1d55 100644 --- a/packages/block-editor/src/components/block-variation-picker/content.scss +++ b/packages/block-editor/src/components/block-variation-picker/content.scss @@ -13,7 +13,7 @@ font-size: $helptext-font-size; svg { - fill: $gray-400 !important; + fill: $gray-600 !important; } .components-button { diff --git a/packages/block-editor/src/components/border-radius-control/all-input-control.js b/packages/block-editor/src/components/border-radius-control/all-input-control.js index 27256f03547f03..14abf3c6c2bc94 100644 --- a/packages/block-editor/src/components/border-radius-control/all-input-control.js +++ b/packages/block-editor/src/components/border-radius-control/all-input-control.js @@ -61,7 +61,7 @@ export default function AllInputControl( { onChange={ handleOnChange } onUnitChange={ handleOnUnitChange } placeholder={ allPlaceholder } - size={ '__unstable-large' } + size="__unstable-large" /> ); } diff --git a/packages/block-editor/src/components/border-radius-control/input-controls.js b/packages/block-editor/src/components/border-radius-control/input-controls.js index c0c5c672d6364f..4529c00b997ac7 100644 --- a/packages/block-editor/src/components/border-radius-control/input-controls.js +++ b/packages/block-editor/src/components/border-radius-control/input-controls.js @@ -80,7 +80,7 @@ export default function BoxInputControls( { onUnitChange={ createHandleOnUnitChange( corner ) } - size={ '__unstable-large' } + size="__unstable-large" /> diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index 974f48e61bc287..cd1289c897824c 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -60,6 +60,8 @@ function ButtonBlockAppender( onClick={ onToggle } aria-haspopup={ isToggleButton ? 'true' : undefined } aria-expanded={ isToggleButton ? isOpen : undefined } + // Disable reason: There shouldn't be a case where this button is disabled but not visually hidden. + // eslint-disable-next-line no-restricted-syntax disabled={ disabled } label={ label } > diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index eb2a02e5095d78..dfc4ee69437f67 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -111,7 +111,7 @@ export default function ChildLayoutControl( { > { selfStretch === 'fixed' && ( { onChange( { selfStretch, @@ -166,7 +166,7 @@ export default function ChildLayoutControl( { panelId={ panelId } > { @@ -181,7 +181,7 @@ export default function ChildLayoutControl( { min={ 1 } /> { @@ -210,7 +210,7 @@ export default function ChildLayoutControl( { > { @@ -234,7 +234,7 @@ export default function ChildLayoutControl( { { diff --git a/packages/block-editor/src/components/colors-gradients/control.js b/packages/block-editor/src/components/colors-gradients/control.js index e820bf73284882..d6b34876c8eaad 100644 --- a/packages/block-editor/src/components/colors-gradients/control.js +++ b/packages/block-editor/src/components/colors-gradients/control.js @@ -21,6 +21,7 @@ import { import { useSettings } from '../use-settings'; import { unlock } from '../../lock-unlock'; +const { Tabs } = unlock( componentsPrivateApis ); const colorsAndGradientKeys = [ 'colors', 'disableCustomColors', @@ -106,11 +107,6 @@ function ColorGradientControlInner( { ); - // Unlocking `Tabs` too early causes the `unlock` method to receive an empty - // object, due to circular dependencies. - // See https://github.com/WordPress/gutenberg/issues/52692 - const { Tabs } = unlock( componentsPrivateApis ); - return ( { tabPanels.color } { tabPanels.gradient } diff --git a/packages/block-editor/src/components/colors/utils.js b/packages/block-editor/src/components/colors/utils.js index d6d51ad0013632..5ec42a6bae7007 100644 --- a/packages/block-editor/src/components/colors/utils.js +++ b/packages/block-editor/src/components/colors/utils.js @@ -17,6 +17,8 @@ import { unlock } from '../../lock-unlock'; extend( [ namesPlugin, a11yPlugin ] ); +const { kebabCase } = unlock( componentsPrivateApis ); + /** * Provided an array of color objects as set by the theme or by the editor defaults, * and the values of the defined color or custom color returns a color object describing the color. @@ -75,8 +77,6 @@ export function getColorClassName( colorContextName, colorSlug ) { return undefined; } - const { kebabCase } = unlock( componentsPrivateApis ); - return `has-${ kebabCase( colorSlug ) }-${ colorContextName }`; } diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index 33079f8b409d6e..680521fb6519c3 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -17,6 +17,8 @@ import { import { useSettings } from '../use-settings'; import { unlock } from '../../lock-unlock'; +const { kebabCase } = unlock( componentsPrivateApis ); + /** * Capitalizes the first letter in a string. * @@ -80,7 +82,6 @@ const withEditorColorPalette = () => * @return {Component} The component that can be used as a HOC. */ function createColorHOC( colorTypes, withColorPalette ) { - const { kebabCase } = unlock( componentsPrivateApis ); const colorMap = colorTypes.reduce( ( colorObject, colorType ) => { return { ...colorObject, diff --git a/packages/block-editor/src/components/contrast-checker/test/index.js b/packages/block-editor/src/components/contrast-checker/test/index.js index 26da2ac0ae8c1f..9abd7d7f7e2e6a 100644 --- a/packages/block-editor/src/components/contrast-checker/test/index.js +++ b/packages/block-editor/src/components/contrast-checker/test/index.js @@ -375,7 +375,7 @@ describe( 'ContrastChecker', () => { render( { render( { test( 'should render nothing when the colors meet AA WCAG guidelines but the background color only has alpha transparency with alpha checker enabled.', () => { const { container } = render( { { render( { test( 'should render component when the colors meet AA WCAG guidelines but all colors have alpha transparency with alpha checker enabled.', () => { render( diff --git a/packages/block-editor/src/components/date-format-picker/index.js b/packages/block-editor/src/components/date-format-picker/index.js index 8c35b025bfccf6..19ec0bf8c24b1d 100644 --- a/packages/block-editor/src/components/date-format-picker/index.js +++ b/packages/block-editor/src/components/date-format-picker/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { _x, __ } from '@wordpress/i18n'; -import { dateI18n } from '@wordpress/date'; +import { dateI18n, humanTimeDiff } from '@wordpress/date'; import { useState, createInterpolateElement } from '@wordpress/element'; import { TextControl, @@ -13,11 +13,15 @@ import { __experimentalVStack as VStack, } from '@wordpress/components'; -// So that we can illustrate the different formats in the dropdown properly, -// show a date that has a day greater than 12 and a month with more than three -// letters. Here we're using 2022-01-25 which is when WordPress 5.9 was -// released. -const EXAMPLE_DATE = new Date( 2022, 0, 25 ); +// So that we illustrate the different formats in the dropdown properly, show a date that is +// somwhat recent, has a day greater than 12, and a month with more than three letters. +const exampleDate = new Date(); +exampleDate.setDate( 20 ); +exampleDate.setMonth( exampleDate.getMonth() - 3 ); +if ( exampleDate.getMonth() === 4 ) { + // May has three letters, so use March. + exampleDate.setMonth( 3 ); +} /** * The `DateFormatPicker` component renders controls that let the user choose a @@ -54,7 +58,7 @@ export default function DateFormatPicker( { label={ __( 'Default format' ) } help={ `${ __( 'Example:' ) } ${ dateI18n( defaultFormat, - EXAMPLE_DATE + exampleDate ) }` } checked={ ! format } onChange={ ( checked ) => @@ -95,13 +99,19 @@ function NonDefaultControls( { format, onChange } ) { ] ), ]; - const suggestedOptions = suggestedFormats.map( - ( suggestedFormat, index ) => ( { + const suggestedOptions = [ + ...suggestedFormats.map( ( suggestedFormat, index ) => ( { key: `suggested-${ index }`, - name: dateI18n( suggestedFormat, EXAMPLE_DATE ), + name: dateI18n( suggestedFormat, exampleDate ), format: suggestedFormat, - } ) - ); + } ) ), + { + key: 'human-diff', + name: humanTimeDiff( exampleDate ), + format: 'human-diff', + }, + ]; + const customOption = { key: 'custom', name: __( 'Custom' ), @@ -111,7 +121,9 @@ function NonDefaultControls( { format, onChange } ) { }; const [ isCustom, setIsCustom ] = useState( - () => !! format && ! suggestedFormats.includes( format ) + () => + !! format && + ! suggestedOptions.some( ( option ) => option.format === format ) ); return ( diff --git a/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js b/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js index e38a01e199b792..d963e71289cb0f 100644 --- a/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js +++ b/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js @@ -92,7 +92,7 @@ export default function AspectRatioTool( { value={ displayValue } options={ options ?? aspectRatioOptions } onChange={ onChange } - size={ '__unstable-large' } + size="__unstable-large" __nextHasNoMarginBottom /> diff --git a/packages/block-editor/src/components/dimensions-tool/scale-tool.js b/packages/block-editor/src/components/dimensions-tool/scale-tool.js index e3ef91745aa53d..05a7d2f4d251a7 100644 --- a/packages/block-editor/src/components/dimensions-tool/scale-tool.js +++ b/packages/block-editor/src/components/dimensions-tool/scale-tool.js @@ -110,7 +110,7 @@ export default function ScaleTool( { help={ scaleHelp[ displayValue ] } value={ displayValue } onChange={ onChange } - size={ '__unstable-large' } + size="__unstable-large" > { options.map( ( option ) => ( diff --git a/packages/block-editor/src/components/font-sizes/utils.js b/packages/block-editor/src/components/font-sizes/utils.js index dff28c7a770d40..b43ed049507aa4 100644 --- a/packages/block-editor/src/components/font-sizes/utils.js +++ b/packages/block-editor/src/components/font-sizes/utils.js @@ -8,6 +8,8 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components'; */ import { unlock } from '../../lock-unlock'; +const { kebabCase } = unlock( componentsPrivateApis ); + /** * Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values. * If namedFontSize is undefined or not found in fontSizes an object with just the size value based on customFontSize is returned. @@ -69,6 +71,5 @@ export function getFontSizeClass( fontSizeSlug ) { return; } - const { kebabCase } = unlock( componentsPrivateApis ); return `has-${ kebabCase( fontSizeSlug ) }-font-size`; } diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index a975de53a99d92..307c742befafda 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -23,7 +23,7 @@ import { __experimentalHStack as HStack, __experimentalTruncate as Truncate, } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { getFilename } from '@wordpress/url'; import { useCallback, Platform, useRef } from '@wordpress/element'; @@ -38,6 +38,7 @@ import { TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; import { store as blockEditorStore } from '../../store'; +import { getResolvedThemeFilePath } from './theme-file-uri-utils'; const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { @@ -82,6 +83,8 @@ export function hasBackgroundSizeValue( style ) { export function hasBackgroundImageValue( style ) { return ( !! style?.background?.backgroundImage?.id || + // Supports url() string values in theme.json. + 'string' === typeof style?.background?.backgroundImage || !! style?.background?.backgroundImage?.url ); } @@ -99,7 +102,7 @@ function backgroundSizeHelpText( value ) { if ( value === 'contain' ) { return __( 'Image is contained without distortion.' ); } - return __( 'Specify a fixed width.' ); + return __( 'Image has a fixed width.' ); } /** @@ -171,13 +174,13 @@ function InspectorImagePreview( { label, filename, url: imgUrl } ) { { imgLabel } - { filename + { imgUrl ? sprintf( /* translators: %s: file name */ - __( 'Selected image: %s' ), - filename + __( 'Background image: %s' ), + filename || imgLabel ) - : __( 'No image selected' ) } + : __( 'No background image selected' ) }
@@ -191,6 +194,7 @@ function BackgroundImageToolsPanelItem( { onChange, style, inheritedValue, + themeFileURIs, } ) { const mediaUpload = useSelect( ( select ) => select( blockEditorStore ).getSettings().mediaUpload, @@ -241,12 +245,22 @@ function BackgroundImageToolsPanelItem( { return; } + const sizeValue = style?.background?.backgroundSize; + const positionValue = style?.background?.backgroundPosition; + onChange( - setImmutably( style, [ 'background', 'backgroundImage' ], { - url: media.url, - id: media.id, - source: 'file', - title: media.title || undefined, + setImmutably( style, [ 'background' ], { + ...style?.background, + backgroundImage: { + url: media.url, + id: media.id, + source: 'file', + title: media.title || undefined, + }, + backgroundPosition: + ! positionValue && ( 'auto' === sizeValue || ! sizeValue ) + ? '50% 0' + : positionValue, } ) ); }; @@ -277,6 +291,23 @@ function BackgroundImageToolsPanelItem( { const hasValue = hasBackgroundImageValue( style ); + const closeAndFocus = () => { + const [ toggleButton ] = focus.tabbable.find( + replaceContainerRef.current + ); + // Focus the toggle button and close the dropdown menu. + // This ensures similar behaviour as to selecting an image, where the dropdown is + // closed and focus is redirected to the dropdown toggle button. + toggleButton?.focus(); + toggleButton?.click(); + }; + + const onRemove = () => + onChange( + setImmutably( style, [ 'background', 'backgroundImage' ], 'none' ) + ); + const canRemove = ! hasValue && hasBackgroundImageValue( inheritedValue ); + return ( } variant="secondary" > + { canRemove && ( + { + closeAndFocus(); + onRemove(); + } } + > + { __( 'Remove' ) } + + ) } { hasValue && ( { - const [ toggleButton ] = focus.tabbable.find( - replaceContainerRef.current - ); - // Focus the toggle button and close the dropdown menu. - // This ensures similar behaviour as to selecting an image, where the dropdown is - // closed and focus is redirected to the dropdown toggle button. - toggleButton?.focus(); - toggleButton?.click(); + closeAndFocus(); resetBackgroundImage(); } } > @@ -340,6 +377,7 @@ function BackgroundSizeToolsPanelItem( { style, inheritedValue, defaultValues, + themeFileURIs, } ) { const sizeValue = style?.background?.backgroundSize || @@ -398,13 +436,16 @@ function BackgroundSizeToolsPanelItem( { const updateBackgroundSize = ( next ) => { // When switching to 'contain' toggle the repeat off. let nextRepeat = repeatValue; + let nextPosition = positionValue; if ( next === 'contain' ) { nextRepeat = 'no-repeat'; + nextPosition = undefined; } if ( next === 'cover' ) { nextRepeat = undefined; + nextPosition = undefined; } if ( @@ -413,11 +454,29 @@ function BackgroundSizeToolsPanelItem( { next === 'auto' ) { nextRepeat = undefined; + /* + * A background image uploaded and set in the editor (an image with a record id), + * receives a default background position of '50% 0', + * when the toggle switches to "Tile". This is to increase the chance that + * the image's focus point is visible. + */ + if ( !! style?.background?.backgroundImage?.id ) { + nextPosition = '50% 0'; + } + } + + /* + * Next will be null when the input is cleared, + * in which case the value should be 'auto'. + */ + if ( ! next && currentValueForToggle === 'auto' ) { + next = 'auto'; } onChange( setImmutably( style, [ 'background' ], { ...style?.background, + backgroundPosition: nextPosition, backgroundRepeat: nextRepeat, backgroundSize: next, } ) @@ -468,50 +527,67 @@ function BackgroundSizeToolsPanelItem( { - { currentValueForToggle !== undefined && - currentValueForToggle !== 'cover' && - currentValueForToggle !== 'contain' ? ( - - ) : null } - { currentValueForToggle !== 'cover' && ( - - ) } + + { currentValueForToggle !== undefined && + currentValueForToggle !== 'cover' && + currentValueForToggle !== 'contain' ? ( + + ) : null } + { currentValueForToggle !== 'cover' && ( + + ) } + ); } @@ -553,6 +629,7 @@ export default function BackgroundPanel( { defaultControls = DEFAULT_CONTROLS, defaultValues = {}, headerLabel = __( 'Background image' ), + themeFileURIs, } ) { const resetAllFilter = useCallback( ( previousValue ) => { return { @@ -577,6 +654,7 @@ export default function BackgroundPanel( { isShownByDefault={ defaultControls.backgroundImage } style={ value } inheritedValue={ inheritedValue } + themeFileURIs={ themeFileURIs } /> { shouldShowBackgroundSizeControls && ( ) } diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js index e02616082cf6a8..a20bb15c044c51 100644 --- a/packages/block-editor/src/components/global-styles/border-panel.js +++ b/packages/block-editor/src/components/global-styles/border-panel.js @@ -19,7 +19,6 @@ import { __ } from '@wordpress/i18n'; import BorderRadiusControl from '../border-radius-control'; import { useColorsPerOrigin } from './hooks'; import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; -import { overrideOrigins } from '../../store/get-block-settings'; import { setImmutably } from '../../utils/object'; import { useBorderPanelLabel } from '../../hooks/border'; import { ShadowPopover, useShadowPresets } from './shadow-panel-components'; @@ -91,7 +90,7 @@ const DEFAULT_CONTROLS = { radius: true, color: true, width: true, - shadow: false, + shadow: true, }; export default function BorderPanel( { @@ -161,9 +160,13 @@ export default function BorderPanel( { // Shadow const shadow = decodeValue( inheritedValue?.shadow ); const shadowPresets = settings?.shadow?.presets ?? {}; - const overriddenShadowPresets = overrideOrigins( shadowPresets ) ?? []; + const mergedShadowPresets = + shadowPresets.custom ?? + shadowPresets.theme ?? + shadowPresets.default ?? + []; const setShadow = ( newValue ) => { - const slug = overriddenShadowPresets?.find( + const slug = mergedShadowPresets?.find( ( { shadow: shadowName } ) => shadowName === newValue )?.slug; @@ -258,7 +261,7 @@ export default function BorderPanel( { popoverPlacement="left-start" value={ border } __experimentalIsRenderedInSidebar - size={ '__unstable-large' } + size="__unstable-large" hideLabelFromVision={ ! hasShadowControl } label={ __( 'Border' ) } /> diff --git a/packages/block-editor/src/components/global-styles/color-panel.js b/packages/block-editor/src/components/global-styles/color-panel.js index 7a20953af428cd..7b49ab453bb453 100644 --- a/packages/block-editor/src/components/global-styles/color-panel.js +++ b/packages/block-editor/src/components/global-styles/color-panel.js @@ -155,6 +155,8 @@ const popoverProps = { shift: true, }; +const { Tabs } = unlock( componentsPrivateApis ); + const LabeledColorIndicators = ( { indicators, label } ) => ( @@ -207,11 +209,7 @@ function ColorPanelDropdown( { panelId, } ) { const currentTab = tabs.find( ( tab ) => tab.userValue !== undefined ); - // Unlocking `Tabs` too early causes the `unlock` method to receive an empty - // object, due to circular dependencies. - // See https://github.com/WordPress/gutenberg/issues/52692 - const { Tabs } = unlock( componentsPrivateApis ); - + const { key: firstTabKey, ...firstTab } = tabs[ 0 ] ?? {}; return ( { tabs.length === 1 && ( { tabs.map( ( tab ) => { + const { key: tabKey, ...restTabProps } = + tab; return ( - { items.map( ( item ) => ( - - ) ) } + { items.map( ( item ) => { + const { key, ...restItem } = item; + return ( + + ); + } ) } { children } ); diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js index 26da27760aa5d0..9718545795f7c8 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -100,14 +100,13 @@ function useHasChildLayout( settings ) { } function useHasSpacingPresets( settings ) { - const { - custom, - theme, - default: defaultPresets, - } = settings?.spacing?.spacingSizes || {}; - const presets = custom ?? theme ?? defaultPresets ?? []; - - return presets.length > 0; + const { defaultSpacingSizes, spacingSizes } = settings?.spacing || {}; + return ( + ( defaultSpacingSizes !== false && + spacingSizes?.default?.length > 0 ) || + spacingSizes?.theme?.length > 0 || + spacingSizes?.custom?.length > 0 + ); } function filterValuesBySides( values, sides ) { diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index e0de34cf2280e2..50211db723e238 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -60,6 +60,7 @@ const VALID_SETTINGS = [ 'position.fixed', 'position.sticky', 'spacing.customSpacingSize', + 'spacing.defaultSpacingSizes', 'spacing.spacingSizes', 'spacing.spacingScale', 'spacing.blockGap', @@ -76,6 +77,7 @@ const VALID_SETTINGS = [ 'typography.fontWeight', 'typography.letterSpacing', 'typography.lineHeight', + 'typography.textAlign', 'typography.textColumns', 'typography.textDecoration', 'typography.textTransform', @@ -87,10 +89,7 @@ export const useGlobalStylesReset = () => { const canReset = !! config && ! fastDeepEqual( config, EMPTY_CONFIG ); return [ canReset, - useCallback( - () => setUserConfig( () => EMPTY_CONFIG ), - [ setUserConfig ] - ), + useCallback( () => setUserConfig( EMPTY_CONFIG ), [ setUserConfig ] ), ]; }; @@ -206,6 +205,11 @@ export function useGlobalStyle( return [ result, setStyle ]; } +export function useGlobalStyleLinks() { + const { merged: mergedConfig } = useContext( GlobalStylesContext ); + return mergedConfig?._links; +} + /** * React hook that overrides a global settings object with block and element specific settings. * @@ -292,6 +296,7 @@ export function useSettingsForBlockElement( 'fontStyle', 'fontWeight', 'letterSpacing', + 'textAlign', 'textTransform', 'textDecoration', 'writingMode', @@ -372,8 +377,13 @@ export function useSettingsForBlockElement( ? updatedSettings.shadow : false; + // Text alignment is only available for blocks. + if ( element ) { + updatedSettings.typography.textAlign = false; + } + return updatedSettings; - }, [ parentSettings, supportedStyles, supports ] ); + }, [ parentSettings, supportedStyles, supports, element ] ); } export function useColorsPerOrigin( settings ) { diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js index 7ad192fac9b4f5..062df0a5606e90 100644 --- a/packages/block-editor/src/components/global-styles/index.js +++ b/packages/block-editor/src/components/global-styles/index.js @@ -3,10 +3,13 @@ export { useGlobalSetting, useGlobalStyle, useSettingsForBlockElement, + useGlobalStyleLinks, } from './hooks'; export { getBlockCSSSelector } from './get-block-css-selector'; export { getLayoutStyles, + getBlockSelectors, + toStyles, useGlobalStylesOutput, useGlobalStylesOutputWithConfig, } from './use-global-styles-output'; diff --git a/packages/block-editor/src/components/global-styles/shadow-panel-components.js b/packages/block-editor/src/components/global-styles/shadow-panel-components.js index 9f2ec416dcb1a9..a30e209027a2bf 100644 --- a/packages/block-editor/src/components/global-styles/shadow-panel-components.js +++ b/packages/block-editor/src/components/global-styles/shadow-panel-components.js @@ -32,6 +32,11 @@ import { unlock } from '../../lock-unlock'; * @type {Array} */ const EMPTY_ARRAY = []; +const { + CompositeItemV2: CompositeItem, + CompositeV2: Composite, + useCompositeStoreV2: useCompositeStore, +} = unlock( componentsPrivateApis ); export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) { const shadows = useShadowPresets( settings ); @@ -59,8 +64,6 @@ export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) { } export function ShadowPresets( { presets, activeShadow, onSelect } ) { - const { CompositeV2: Composite, useCompositeStoreV2: useCompositeStore } = - unlock( componentsPrivateApis ); const compositeStore = useCompositeStore(); return ! presets ? null : ( { + const themeJson = { + styles: { + background: { + backgroundImage: { + url: 'file:./assets/image.jpg', + }, + }, + }, + }; + + it( 'should replace relative paths with resolved URIs if found in themeFileURIs', () => { + const newThemeJson = setThemeFileUris( themeJson, themeFileURIs ); + expect( + newThemeJson.styles.background.backgroundImage.url === + 'https://wordpress.org/assets/image.jpg' + ).toBe( true ); + // Object reference should be the same as the function is mutating the object. + expect( newThemeJson ).toEqual( themeJson ); + } ); +} ); + +describe( 'getResolvedThemeFilePath()', () => { + it.each( [ + [ + 'file:./assets/image.jpg', + 'https://wordpress.org/assets/image.jpg', + 'Should return absolute URL if found in themeFileURIs', + ], + [ + 'file:./misc/image.jpg', + 'file:./misc/image.jpg', + 'Should return value if not found in themeFileURIs', + ], + [ + 'https://wordpress.org/assets/image.jpg', + 'https://wordpress.org/assets/image.jpg', + 'Should not match absolute URLs', + ], + ] )( 'Given file %s and return value %s: %s', ( file, returnedValue ) => { + expect( + getResolvedThemeFilePath( file, themeFileURIs ) === returnedValue + ).toBe( true ); + } ); +} ); diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 95de3d4007079c..b858a2e4140ea9 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -480,7 +480,7 @@ describe( 'global styles renderer', () => { }; expect( toStyles( tree, blockSelectors ) ).toEqual( - ':where(body) {margin: 0;}.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > :is(*, div) { margin: 0; }body .is-layout-grid { display:grid; }.is-layout-grid > :is(*, div) { margin: 0; }:where(body){background-color: red;margin: 10px;padding: 10px;}:where(a:where(:not(.wp-element-button))){color: blue;}a:where(:not(.wp-element-button)):hover{color: orange;}a:where(:not(.wp-element-button)):focus{color: orange;}:where(h1){font-size: 42px;}:where(.wp-block-group){margin-top: 10px;margin-right: 20px;margin-bottom: 30px;margin-left: 40px;padding-top: 11px;padding-right: 22px;padding-bottom: 33px;padding-left: 44px;}:where(h1,h2,h3,h4,h5,h6){color: orange;}:where(h1 a:where(:not(.wp-element-button)),h2 a:where(:not(.wp-element-button)),h3 a:where(:not(.wp-element-button)),h4 a:where(:not(.wp-element-button)),h5 a:where(:not(.wp-element-button)),h6 a:where(:not(.wp-element-button))){color: hotpink;}h1 a:where(:not(.wp-element-button)):hover,h2 a:where(:not(.wp-element-button)):hover,h3 a:where(:not(.wp-element-button)):hover,h4 a:where(:not(.wp-element-button)):hover,h5 a:where(:not(.wp-element-button)):hover,h6 a:where(:not(.wp-element-button)):hover{color: red;}h1 a:where(:not(.wp-element-button)):focus,h2 a:where(:not(.wp-element-button)):focus,h3 a:where(:not(.wp-element-button)):focus,h4 a:where(:not(.wp-element-button)):focus,h5 a:where(:not(.wp-element-button)):focus,h6 a:where(:not(.wp-element-button)):focus{color: red;}:where(.wp-block-image img, .wp-block-image .wp-crop-area){border-radius: 9999px;}:where(.wp-block-image){color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.has-white-color{color: var(--wp--preset--color--white) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}h1.has-blue-color,h2.has-blue-color,h3.has-blue-color,h4.has-blue-color,h5.has-blue-color,h6.has-blue-color{color: var(--wp--preset--color--blue) !important;}h1.has-blue-background-color,h2.has-blue-background-color,h3.has-blue-background-color,h4.has-blue-background-color,h5.has-blue-background-color,h6.has-blue-background-color{background-color: var(--wp--preset--color--blue) !important;}h1.has-blue-border-color,h2.has-blue-border-color,h3.has-blue-border-color,h4.has-blue-border-color,h5.has-blue-border-color,h6.has-blue-border-color{border-color: var(--wp--preset--color--blue) !important;}' + ':where(body) {margin: 0;}.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > :is(*, div) { margin: 0; }body .is-layout-grid { display:grid; }.is-layout-grid > :is(*, div) { margin: 0; }:root :where(body){background-color: red;margin: 10px;padding: 10px;}:root :where(a:where(:not(.wp-element-button))){color: blue;}a:where(:not(.wp-element-button)):hover{color: orange;}a:where(:not(.wp-element-button)):focus{color: orange;}:root :where(h1){font-size: 42px;}:root :where(.wp-block-group){margin-top: 10px;margin-right: 20px;margin-bottom: 30px;margin-left: 40px;padding-top: 11px;padding-right: 22px;padding-bottom: 33px;padding-left: 44px;}:root :where(h1,h2,h3,h4,h5,h6){color: orange;}:root :where(h1 a:where(:not(.wp-element-button)),h2 a:where(:not(.wp-element-button)),h3 a:where(:not(.wp-element-button)),h4 a:where(:not(.wp-element-button)),h5 a:where(:not(.wp-element-button)),h6 a:where(:not(.wp-element-button))){color: hotpink;}h1 a:where(:not(.wp-element-button)):hover,h2 a:where(:not(.wp-element-button)):hover,h3 a:where(:not(.wp-element-button)):hover,h4 a:where(:not(.wp-element-button)):hover,h5 a:where(:not(.wp-element-button)):hover,h6 a:where(:not(.wp-element-button)):hover{color: red;}h1 a:where(:not(.wp-element-button)):focus,h2 a:where(:not(.wp-element-button)):focus,h3 a:where(:not(.wp-element-button)):focus,h4 a:where(:not(.wp-element-button)):focus,h5 a:where(:not(.wp-element-button)):focus,h6 a:where(:not(.wp-element-button)):focus{color: red;}:root :where(.wp-block-image img, .wp-block-image .wp-crop-area){border-radius: 9999px;}:root :where(.wp-block-image){color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.has-white-color{color: var(--wp--preset--color--white) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}h1.has-blue-color,h2.has-blue-color,h3.has-blue-color,h4.has-blue-color,h5.has-blue-color,h6.has-blue-color{color: var(--wp--preset--color--blue) !important;}h1.has-blue-background-color,h2.has-blue-background-color,h3.has-blue-background-color,h4.has-blue-background-color,h5.has-blue-background-color,h6.has-blue-background-color{background-color: var(--wp--preset--color--blue) !important;}h1.has-blue-border-color,h2.has-blue-border-color,h3.has-blue-border-color,h4.has-blue-border-color,h5.has-blue-border-color,h6.has-blue-border-color{border-color: var(--wp--preset--color--blue) !important;}' ); } ); @@ -520,7 +520,7 @@ describe( 'global styles renderer', () => { }; expect( toStyles( Object.freeze( tree ), blockSelectors ) ).toEqual( - ':where(body) {margin: 0;}.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > :is(*, div) { margin: 0; }body .is-layout-grid { display:grid; }.is-layout-grid > :is(*, div) { margin: 0; }:where(.wp-image-spacing){padding-top: 1px;}:where(.wp-image-border-color){border-color: red;}:where(.wp-image-border){border-radius: 9999px;}:where(.wp-image){color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' + ':where(body) {margin: 0;}.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > :is(*, div) { margin: 0; }body .is-layout-grid { display:grid; }.is-layout-grid > :is(*, div) { margin: 0; }:root :where(.wp-image-spacing){padding-top: 1px;}:root :where(.wp-image-border-color){border-color: red;}:root :where(.wp-image-border){border-radius: 9999px;}:root :where(.wp-image){color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' ); } ); @@ -566,7 +566,8 @@ describe( 'global styles renderer', () => { }; expect( toStyles( Object.freeze( tree ), blockSelectors ) ).toEqual( - ':where(body) {margin: 0;}.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > :is(*, div) { margin: 0; }body .is-layout-grid { display:grid; }.is-layout-grid > :is(*, div) { margin: 0; }.is-style-foo.wp-image.wp-image-spacing{padding-top: 2px;}.is-style-foo.wp-image.wp-image-border-color{border-color: blue;}.is-style-foo.wp-image{color: blue;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' + ':where(body) {margin: 0;}.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-constrained > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-constrained > .aligncenter { margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: var(--wp--style--global--content-size); margin-left: auto !important; margin-right: auto !important; }.is-layout-constrained > .alignwide { max-width: var(--wp--style--global--wide-size); }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > :is(*, div) { margin: 0; }body .is-layout-grid { display:grid; }.is-layout-grid > :is(*, div) { margin: 0; }' + + ':root :where(.is-style-foo.wp-image.wp-image-spacing){padding-top: 2px;}:root :where(.is-style-foo.wp-image.wp-image-border-color){border-color: blue;}:root :where(.is-style-foo.wp-image){color: blue;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' ); } ); @@ -725,7 +726,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - ':where(.is-layout-flow) > * { margin-block-start: 0; margin-block-end: 0; }:where(.is-layout-flow) > * + * { margin-block-start: 0.5em; margin-block-end: 0; }:where(.is-layout-flex) { gap: 0.5em; }:root { --wp--style--block-gap: 0.5em; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }' + '.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }.is-layout-flex { gap: 0.5em; }:root { --wp--style--block-gap: 0.5em; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }' ); } ); @@ -742,7 +743,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - ':where(.is-layout-flow) > * { margin-block-start: 0; margin-block-end: 0; }:where(.is-layout-flow) > * + * { margin-block-start: 12px; margin-block-end: 0; }:where(.is-layout-flex) { gap: 12px; }:root { --wp--style--block-gap: 12px; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }' + '.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.is-layout-flex { gap: 12px; }:root { --wp--style--block-gap: 12px; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }' ); } ); @@ -759,7 +760,7 @@ describe( 'global styles renderer', () => { } ); expect( layoutStyles ).toEqual( - ':where(.wp-block-group-is-layout-flow) > * { margin-block-start: 0; margin-block-end: 0; }:where(.wp-block-group-is-layout-flow) > * + * { margin-block-start: 12px; margin-block-end: 0; }:where(.wp-block-group-is-layout-flex) { gap: 12px; }' + '.wp-block-group-is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.wp-block-group-is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.wp-block-group-is-layout-flex { gap: 12px; }' ); } ); @@ -996,7 +997,7 @@ describe( 'global styles renderer', () => { it( 'should return processed CSS without any nested selectors', () => { expect( processCSSNesting( 'color: red; margin: auto;', '.foo' ) - ).toEqual( '.foo{color: red; margin: auto;}' ); + ).toEqual( ':root :where(.foo){color: red; margin: auto;}' ); } ); it( 'should return processed CSS with nested selectors', () => { expect( @@ -1005,7 +1006,7 @@ describe( 'global styles renderer', () => { '.foo' ) ).toEqual( - '.foo{color: red; margin: auto;}.foo.one{color: blue;}.foo .two{color: green;}' + ':root :where(.foo){color: red; margin: auto;}:root :where(.foo.one){color: blue;}:root :where(.foo .two){color: green;}' ); } ); it( 'should return processed CSS with pseudo elements', () => { @@ -1015,7 +1016,7 @@ describe( 'global styles renderer', () => { '.foo' ) ).toEqual( - '.foo{color: red; margin: auto;}.foo::before{color: blue;}.foo ::before{color: green;}.foo.one::before{color: yellow;}.foo .two::before{color: purple;}' + ':root :where(.foo){color: red; margin: auto;}:root :where(.foo::before){color: blue;}:root :where(.foo ::before){color: green;}:root :where(.foo.one::before){color: yellow;}:root :where(.foo .two::before){color: purple;}' ); } ); it( 'should return processed CSS with multiple root selectors', () => { @@ -1025,7 +1026,7 @@ describe( 'global styles renderer', () => { '.foo, .bar' ) ).toEqual( - '.foo, .bar{color: red; margin: auto;}.foo.one, .bar.one{color: blue;}.foo .two, .bar .two{color: green;}.foo::before, .bar::before{color: yellow;}.foo ::before, .bar ::before{color: purple;}.foo.three::before, .bar.three::before{color: orange;}.foo .four::before, .bar .four::before{color: skyblue;}' + ':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo::before, .bar::before){color: yellow;}:root :where(.foo ::before, .bar ::before){color: purple;}:root :where(.foo.three::before, .bar.three::before){color: orange;}:root :where(.foo .four::before, .bar .four::before){color: skyblue;}' ); } ); } ); diff --git a/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js b/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js new file mode 100644 index 00000000000000..1ab05a45f0d54b --- /dev/null +++ b/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js @@ -0,0 +1,77 @@ +/** + * Internal dependencies + */ +import { getValueFromObjectPath } from '../../utils/object'; + +/** + * Looks up a theme file URI based on a relative path. + * + * @param {string} file A relative path. + * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. + * @return {string?} A resolved theme file URI, if one is found in the themeFileURIs collection. + */ +export function getResolvedThemeFilePath( file, themeFileURIs = [] ) { + const uri = themeFileURIs.find( + ( themeFileUri ) => themeFileUri.name === file + ); + + if ( ! uri?.href ) { + return file; + } + + return uri?.href; +} + +/** + * Mutates an object by settings a value at the provided path. + * + * @param {Object} object Object to set a value in. + * @param {number|string|Array} path Path in the object to modify. + * @param {*} value New value to set. + * @return {Object} Object with the new value set. + */ +function setMutably( object, path, value ) { + path = path.split( '.' ); + const finalValueKey = path.pop(); + let prev = object; + + for ( const key of path ) { + const current = prev[ key ]; + prev = current; + } + + prev[ finalValueKey ] = value; + + return object; +} + +/** + * Resolves any relative paths if a corresponding theme file URI is available. + * Note: this function mutates the object and is specifically to be used in + * an async styles build context in useGlobalStylesOutput + * + * @param {Object} themeJson Theme.json/Global styles tree. + * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. + * @return {Object} Returns mutated object. + */ +export function setThemeFileUris( themeJson, themeFileURIs ) { + if ( ! themeJson?.styles || ! themeFileURIs ) { + return themeJson; + } + + themeFileURIs.forEach( ( { name, href, target } ) => { + const value = getValueFromObjectPath( themeJson, target ); + if ( value === name ) { + /* + * The object must not be updated immutably here because the + * themeJson is a reference to the global styles tree used as a dependency in the + * useGlobalStylesOutputWithConfig() hook. If we don't mutate the object, + * the hook will detect the change and re-render the component, resulting + * in a maximum depth exceeded error. + */ + themeJson = setMutably( themeJson, target, href ); + } + } ); + + return themeJson; +} diff --git a/packages/block-editor/src/components/global-styles/typography-panel.js b/packages/block-editor/src/components/global-styles/typography-panel.js index 76836c775bb807..3106723945fe86 100644 --- a/packages/block-editor/src/components/global-styles/typography-panel.js +++ b/packages/block-editor/src/components/global-styles/typography-panel.js @@ -8,16 +8,16 @@ import { __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; /** * Internal dependencies */ -import { mergeOrigins, hasOriginValue } from '../../store/get-block-settings'; import FontFamilyControl from '../font-family'; import FontAppearanceControl from '../font-appearance-control'; import LineHeightControl from '../line-height-control'; import LetterSpacingControl from '../letter-spacing-control'; +import TextAlignmentControl from '../text-alignment-control'; import TextTransformControl from '../text-transform-control'; import TextDecorationControl from '../text-decoration-control'; import WritingModeControl from '../writing-mode-control'; @@ -32,6 +32,7 @@ export function useHasTypographyPanel( settings ) { const hasLineHeight = useHasLineHeightControl( settings ); const hasFontAppearance = useHasAppearanceControl( settings ); const hasLetterSpacing = useHasLetterSpacingControl( settings ); + const hasTextAlign = useHasTextAlignmentControl( settings ); const hasTextTransform = useHasTextTransformControl( settings ); const hasTextDecoration = useHasTextDecorationControl( settings ); const hasWritingMode = useHasWritingModeControl( settings ); @@ -43,6 +44,7 @@ export function useHasTypographyPanel( settings ) { hasLineHeight || hasFontAppearance || hasLetterSpacing || + hasTextAlign || hasTextTransform || hasFontSize || hasTextDecoration || @@ -62,7 +64,9 @@ function useHasFontSizeControl( settings ) { } function useHasFontFamilyControl( settings ) { - return hasOriginValue( settings?.typography?.fontFamilies ); + return [ 'default', 'theme', 'custom' ].some( + ( key ) => settings?.typography?.fontFamilies?.[ key ]?.length + ); } function useHasLineHeightControl( settings ) { @@ -91,6 +95,10 @@ function useHasTextTransformControl( settings ) { return settings?.typography?.textTransform; } +function useHasTextAlignmentControl( settings ) { + return settings?.typography?.textAlign; +} + function useHasTextDecorationControl( settings ) { return settings?.typography?.textDecoration; } @@ -150,6 +158,7 @@ const DEFAULT_CONTROLS = { fontAppearance: true, lineHeight: true, letterSpacing: true, + textAlign: true, textTransform: true, textDecoration: true, writingMode: true, @@ -170,8 +179,12 @@ export default function TypographyPanel( { // Font Family const hasFontFamilyEnabled = useHasFontFamilyControl( settings ); - const fontFamilies = settings?.typography?.fontFamilies ?? {}; - const mergedFontFamilies = fontFamilies ? mergeOrigins( fontFamilies ) : []; + const fontFamilies = settings?.typography?.fontFamilies; + const mergedFontFamilies = useMemo( () => { + return [ 'default', 'theme', 'custom' ].flatMap( + ( key ) => fontFamilies?.[ key ] ?? [] + ); + }, [ fontFamilies ] ); const fontFamily = decodeValue( inheritedValue?.typography?.fontFamily ); const setFontFamily = ( newValue ) => { const slug = mergedFontFamilies?.find( @@ -334,6 +347,22 @@ export default function TypographyPanel( { const hasWritingMode = () => !! value?.typography?.writingMode; const resetWritingMode = () => setWritingMode( undefined ); + // Text Alignment + const hasTextAlignmentControl = useHasTextAlignmentControl( settings ); + + const textAlign = decodeValue( inheritedValue?.typography?.textAlign ); + const setTextAlign = ( newValue ) => { + onChange( + setImmutably( + value, + [ 'typography', 'textAlign' ], + newValue || undefined + ) + ); + }; + const hasTextAlign = () => !! value?.typography?.textAlign; + const resetTextAlign = () => setTextAlign( undefined ); + const resetAllFilter = useCallback( ( previousValue ) => { return { ...previousValue, @@ -514,6 +543,22 @@ export default function TypographyPanel( { /> ) } + { hasTextAlignmentControl && ( + + + + ) } ); } diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 23bb88efc9991e..68839ea15d775e 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -21,6 +21,7 @@ import { ROOT_BLOCK_SELECTOR, ROOT_CSS_PROPERTIES_SELECTOR, scopeSelector, + scopeFeatureSelectors, appendToSelector, getBlockStyleVariationSelector, } from './utils'; @@ -33,8 +34,8 @@ import { getGapCSSValue } from '../../hooks/gap'; import { store as blockEditorStore } from '../../store'; import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; -import BlockContext from '../block-context'; import { unlock } from '../../lock-unlock'; +import { setThemeFileUris } from './theme-file-uri-utils'; // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. @@ -44,6 +45,7 @@ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = { spacing: 'spacing', typography: 'typography', }; +const { kebabCase } = unlock( componentsPrivateApis ); function compileStyleValue( uncompiledValue ) { const VARIABLE_REFERENCE_PREFIX = 'var:'; @@ -69,8 +71,6 @@ function compileStyleValue( uncompiledValue ) { * @return {Array} An array of style declarations. */ function getPresetsDeclarations( blockPresets = {}, mergedSettings ) { - const { kebabCase } = unlock( componentsPrivateApis ); - return PRESET_METADATA.reduce( ( declarations, { path, valueKey, valueFunc, cssVarInfix } ) => { const presetByOrigin = getValueFromObjectPath( @@ -115,8 +115,6 @@ function getPresetsDeclarations( blockPresets = {}, mergedSettings ) { * @return {string} CSS declarations for the preset classes. */ function getPresetsClasses( blockSelector = '*', blockPresets = {} ) { - const { kebabCase } = unlock( componentsPrivateApis ); - return PRESET_METADATA.reduce( ( declarations, { path, cssVarInfix, classes } ) => { if ( ! classes ) { @@ -181,7 +179,6 @@ function getPresetsSvgFilters( blockPresets = {} ) { } function flattenTree( input = {}, prefix, token ) { - const { kebabCase } = unlock( componentsPrivateApis ); let result = []; Object.keys( input ).forEach( ( key ) => { const newKey = prefix + kebabCase( key.replace( '/', '-' ) ); @@ -313,7 +310,7 @@ const getFeatureDeclarations = ( selectors, styles ) => { * * @param {Object} tree A theme.json tree containing layout definitions. * - * @param {boolean} isTemplate Whether the entity being edited is a full template or a pattern. + * @param {boolean} disableRootPadding Whether to force disable the root padding styles. * @return {Array} An array of style declarations. */ export function getStylesDeclarations( @@ -321,9 +318,8 @@ export function getStylesDeclarations( selector = '', useRootPaddingAlign, tree = {}, - isTemplate = true + disableRootPadding = false ) { - const { kebabCase } = unlock( componentsPrivateApis ); const isRoot = ROOT_BLOCK_SELECTOR === selector; const output = Object.entries( STYLE_PROPERTY ).reduce( ( @@ -398,7 +394,7 @@ export function getStylesDeclarations( // Don't output padding properties if padding variables are set or if we're not editing a full template. if ( isRoot && - ( useRootPaddingAlign || ! isTemplate ) && + ( useRootPaddingAlign || disableRootPadding ) && rule.key.startsWith( 'padding' ) ) { return; @@ -527,10 +523,10 @@ export function getLayoutStyles( { } else { combinedSelector = selector === ROOT_BLOCK_SELECTOR - ? `:where(.${ className })${ + ? `.${ className }${ spacingStyle?.selector || '' }` - : `:where(${ selector }-${ className })${ + : `${ selector }-${ className }${ spacingStyle?.selector || '' }`; } @@ -651,14 +647,112 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => { if ( node?.variations ) { const variations = {}; - Object.keys( node.variations ).forEach( ( variation ) => { - variations[ variation ] = pickStyleKeys( - node.variations[ variation ] - ); - } ); + Object.entries( node.variations ).forEach( + ( [ variationName, variation ] ) => { + variations[ variationName ] = + pickStyleKeys( variation ); + if ( variation?.css ) { + variations[ variationName ].css = variation.css; + } + const variationSelector = + blockSelectors[ blockName ] + .styleVariationSelectors?.[ variationName ]; + + // Process the variation's inner element styles. + // This comes before the inner block styles so the + // element styles within the block type styles take + // precedence over these. + Object.entries( variation?.elements ?? {} ).forEach( + ( [ element, elementStyles ] ) => { + if ( elementStyles && ELEMENTS[ element ] ) { + nodes.push( { + styles: elementStyles, + selector: scopeSelector( + variationSelector, + ELEMENTS[ element ] + ), + } ); + } + } + ); + + // Process the variations inner block type styles. + Object.entries( variation?.blocks ?? {} ).forEach( + ( [ + variationBlockName, + variationBlockStyles, + ] ) => { + const variationBlockSelector = scopeSelector( + variationSelector, + blockSelectors[ variationBlockName ] + .selector + ); + const variationDuotoneSelector = scopeSelector( + variationSelector, + blockSelectors[ variationBlockName ] + .duotoneSelector + ); + const variationFeatureSelectors = + scopeFeatureSelectors( + variationSelector, + blockSelectors[ variationBlockName ] + .featureSelectors + ); + + const variationBlockStyleNodes = + pickStyleKeys( variationBlockStyles ); + + if ( variationBlockStyles?.css ) { + variationBlockStyleNodes.css = + variationBlockStyles.css; + } + + nodes.push( { + selector: variationBlockSelector, + duotoneSelector: variationDuotoneSelector, + featureSelectors: variationFeatureSelectors, + fallbackGapValue: + blockSelectors[ variationBlockName ] + .fallbackGapValue, + hasLayoutSupport: + blockSelectors[ variationBlockName ] + .hasLayoutSupport, + styles: variationBlockStyleNodes, + } ); + + // Process element styles for the inner blocks + // of the variation. + Object.entries( + variationBlockStyles.elements ?? {} + ).forEach( + ( [ + variationBlockElement, + variationBlockElementStyles, + ] ) => { + if ( + variationBlockElementStyles && + ELEMENTS[ variationBlockElement ] + ) { + nodes.push( { + styles: variationBlockElementStyles, + selector: scopeSelector( + variationBlockSelector, + ELEMENTS[ + variationBlockElement + ] + ), + } ); + } + } + ); + } + ); + } + ); blockStyles.variations = variations; } - if ( blockStyles && blockSelectors?.[ blockName ]?.selector ) { + + if ( blockSelectors?.[ blockName ]?.selector ) { nodes.push( { duotoneSelector: blockSelectors[ blockName ].duotoneSelector, @@ -776,7 +870,7 @@ export const toStyles = ( hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles = false, - isTemplate = true, + disableRootPadding = false, styleOptions = undefined ) => { // These allow opting out of certain sets of styles. @@ -820,19 +914,18 @@ export const toStyles = ( */ ruleset += ':where(body) {margin: 0;'; - // Root padding styles should only be output for full templates, not patterns or template parts. - if ( options.rootPadding && useRootPaddingAlign && isTemplate ) { + // Root padding styles should be output for full templates, patterns and template parts. + if ( options.rootPadding && useRootPaddingAlign ) { /* * These rules reproduce the ones from https://github.com/WordPress/gutenberg/blob/79103f124925d1f457f627e154f52a56228ed5ad/lib/class-wp-theme-json-gutenberg.php#L2508 * almost exactly, but for the selectors that target block wrappers in the front end. This code only runs in the editor, so it doesn't need those selectors. */ ruleset += `padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) } .has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } - .has-global-padding :where(.has-global-padding:not(.wp-block-block)) { padding-right: 0; padding-left: 0; } .has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); } - .has-global-padding :where(.has-global-padding:not(.wp-block-block)) > .alignfull { margin-right: 0; margin-left: 0; } - .has-global-padding > .alignfull:where(:not(.has-global-padding):not(.is-layout-flex):not(.is-layout-grid)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } - .has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0;`; + .has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; } + .has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; + `; } ruleset += '}'; @@ -861,12 +954,62 @@ export const toStyles = ( ( [ cssSelector, declarations ] ) => { if ( declarations.length ) { const rules = declarations.join( ';' ); - ruleset += `:where(${ cssSelector }){${ rules };}`; + ruleset += `:root :where(${ cssSelector }){${ rules };}`; } } ); } + // Process duotone styles. + if ( duotoneSelector ) { + const duotoneStyles = {}; + if ( styles?.filter ) { + duotoneStyles.filter = styles.filter; + delete styles.filter; + } + const duotoneDeclarations = + getStylesDeclarations( duotoneStyles ); + if ( duotoneDeclarations.length ) { + ruleset += `${ duotoneSelector }{${ duotoneDeclarations.join( + ';' + ) };}`; + } + } + + // Process blockGap and layout styles. + if ( + ! disableLayoutStyles && + ( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport ) + ) { + ruleset += getLayoutStyles( { + style: styles, + selector, + hasBlockGapSupport, + hasFallbackGapSupport, + fallbackGapValue, + } ); + } + + // Process the remaining block styles (they use either normal block class or __experimentalSelector). + const styleDeclarations = getStylesDeclarations( + styles, + selector, + useRootPaddingAlign, + tree, + disableRootPadding + ); + if ( styleDeclarations?.length ) { + ruleset += `:root :where(${ selector }){${ styleDeclarations.join( + ';' + ) };}`; + } + if ( styles?.css ) { + ruleset += processCSSNesting( + styles.css, + `:root :where(${ selector })` + ); + } + if ( styleVariationSelectors ) { Object.entries( styleVariationSelectors ).forEach( ( [ styleVariationName, styleVariationSelector ] ) => { @@ -893,7 +1036,7 @@ export const toStyles = ( ); const rules = declarations.join( ';' ); - ruleset += `${ cssSelector }{${ rules };}`; + ruleset += `:root :where(${ cssSelector }){${ rules };}`; } } ); @@ -908,59 +1051,21 @@ export const toStyles = ( tree ); if ( styleVariationDeclarations.length ) { - ruleset += `${ styleVariationSelector }{${ styleVariationDeclarations.join( + ruleset += `:root :where(${ styleVariationSelector }){${ styleVariationDeclarations.join( ';' ) };}`; } + if ( styleVariations?.css ) { + ruleset += processCSSNesting( + styleVariations.css, + `:root :where(${ styleVariationSelector })` + ); + } } } ); } - // Process duotone styles. - if ( duotoneSelector ) { - const duotoneStyles = {}; - if ( styles?.filter ) { - duotoneStyles.filter = styles.filter; - delete styles.filter; - } - const duotoneDeclarations = - getStylesDeclarations( duotoneStyles ); - if ( duotoneDeclarations.length ) { - ruleset += `${ duotoneSelector }{${ duotoneDeclarations.join( - ';' - ) };}`; - } - } - - // Process blockGap and layout styles. - if ( - ! disableLayoutStyles && - ( ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport ) - ) { - ruleset += getLayoutStyles( { - style: styles, - selector, - hasBlockGapSupport, - hasFallbackGapSupport, - fallbackGapValue, - } ); - } - - // Process the remaining block styles (they use either normal block class or __experimentalSelector). - const declarations = getStylesDeclarations( - styles, - selector, - useRootPaddingAlign, - tree, - isTemplate - ); - if ( declarations?.length ) { - ruleset += `:where(${ selector }){${ declarations.join( - ';' - ) };}`; - } - // Check for pseudo selector in `styles` and handle separately. const pseudoSelectorStyles = Object.entries( styles ).filter( ( [ key ] ) => key.startsWith( ':' ) @@ -976,7 +1081,7 @@ export const toStyles = ( return; } - // `selector` maybe provided in a form + // `selector` may be provided in a form // where block level selectors have sub element // selectors appended to them as a comma separated // string. @@ -1019,13 +1124,13 @@ export const toStyles = ( getGapCSSValue( tree?.styles?.spacing?.blockGap ) || '0.5em'; ruleset = ruleset + - `:where(.wp-site-blocks) > * { margin-block-start: ${ gapValue }; margin-block-end: 0; }`; + `:root :where(.wp-site-blocks) > * { margin-block-start: ${ gapValue }; margin-block-end: 0; }`; ruleset = ruleset + - ':where(.wp-site-blocks) > :first-child { margin-block-start: 0; }'; + ':root :where(.wp-site-blocks) > :first-child { margin-block-start: 0; }'; ruleset = ruleset + - ':where(.wp-site-blocks) > :last-child { margin-block-end: 0; }'; + ':root :where(.wp-site-blocks) > :last-child { margin-block-end: 0; }'; } if ( options.presets ) { @@ -1080,7 +1185,11 @@ const getSelectorsConfig = ( blockType, rootSelector ) => { return config; }; -export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { +export const getBlockSelectors = ( + blockTypes, + getBlockStyles, + variationInstanceId +) => { const result = {}; blockTypes.forEach( ( blockType ) => { const name = blockType.name; @@ -1110,16 +1219,19 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const blockStyleVariations = getBlockStyles( name ); const styleVariationSelectors = {}; - if ( blockStyleVariations?.length ) { - blockStyleVariations.forEach( ( variation ) => { - const styleVariationSelector = getBlockStyleVariationSelector( - variation.name, - selector - ); - styleVariationSelectors[ variation.name ] = - styleVariationSelector; - } ); - } + blockStyleVariations?.forEach( ( variation ) => { + const variationSuffix = variationInstanceId + ? `-${ variationInstanceId }` + : ''; + const variationName = `${ variation.name }${ variationSuffix }`; + const styleVariationSelector = getBlockStyleVariationSelector( + variationName, + selector + ); + + styleVariationSelectors[ variationName ] = styleVariationSelector; + } ); + // For each block support feature add any custom selectors. const featureSelectors = getSelectorsConfig( blockType, selector ); @@ -1132,8 +1244,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { hasLayoutSupport, name, selector, - styleVariationSelectors: Object.keys( styleVariationSelectors ) - .length + styleVariationSelectors: blockStyleVariations?.length ? styleVariationSelectors : undefined, }; @@ -1186,7 +1297,7 @@ export function processCSSNesting( css, blockSelector ) { const isRootCss = ! part.includes( '{' ); if ( isRootCss ) { // If the part doesn't contain braces, it applies to the root level. - processedCSS += `${ blockSelector }{${ part.trim() }}`; + processedCSS += `:root :where(${ blockSelector }){${ part.trim() }}`; } else { // If the part contains braces, it's a nested CSS rule. const splittedPart = part.replace( '}', '' ).split( '{' ); @@ -1199,7 +1310,7 @@ export function processCSSNesting( css, blockSelector ) { ? scopeSelector( blockSelector, nestedSelector ) : appendToSelector( blockSelector, nestedSelector ); - processedCSS += `${ combinedSelector }{${ cssValue.trim() }}`; + processedCSS += `:root :where(${ combinedSelector }){${ cssValue.trim() }}`; } } ); return processedCSS; @@ -1212,11 +1323,20 @@ export function processCSSNesting( css, blockSelector ) { * The use case for a custom config is to generate bespoke styles * and settings for previews, or other out-of-editor experiences. * - * @param {Object} mergedConfig Global styles configuration. + * @param {Object} mergedConfig Global styles configuration. + * @param {boolean} disableRootPadding Disable root padding styles. + * * @return {Array} Array of stylesheets and settings. */ -export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { +export function useGlobalStylesOutputWithConfig( + mergedConfig = {}, + disableRootPadding +) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); + mergedConfig = setThemeFileUris( + mergedConfig, + mergedConfig?._links?.[ 'wp:theme-file' ] + ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. const disableLayoutStyles = useSelect( ( select ) => { @@ -1224,10 +1344,6 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { return !! getSettings().disableLayoutStyles; } ); - const blockContext = useContext( BlockContext ); - - const isTemplate = blockContext?.templateSlug !== undefined; - const { getBlockStyles } = useSelect( blocksStore ); return useMemo( () => { @@ -1252,7 +1368,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles, - isTemplate + disableRootPadding ); const svgs = toSvgFilters( updatedConfig, blockSelectors ); @@ -1299,7 +1415,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { hasFallbackGapSupport, mergedConfig, disableLayoutStyles, - isTemplate, + disableRootPadding, getBlockStyles, ] ); } @@ -1307,9 +1423,11 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { /** * Returns the global styles output based on the current state of global styles config loaded in the editor context. * + * @param {boolean} disableRootPadding Disable root padding styles. + * * @return {Array} Array of stylesheets and settings. */ -export function useGlobalStylesOutput() { +export function useGlobalStylesOutput( disableRootPadding = false ) { const { merged: mergedConfig } = useContext( GlobalStylesContext ); - return useGlobalStylesOutputWithConfig( mergedConfig ); + return useGlobalStylesOutputWithConfig( mergedConfig, disableRootPadding ); } diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js index 23738378b69983..71753a67beb021 100644 --- a/packages/block-editor/src/components/height-control/index.js +++ b/packages/block-editor/src/components/height-control/index.js @@ -156,7 +156,7 @@ export default function HeightControl( { onChange={ onChange } onUnitChange={ handleUnitChange } min={ 0 } - size={ '__unstable-large' } + size="__unstable-large" label={ label } hideLabelFromVision /> diff --git a/packages/block-editor/src/components/iframe/content.scss b/packages/block-editor/src/components/iframe/content.scss index 62a57030f1be11..165cf70d24a710 100644 --- a/packages/block-editor/src/components/iframe/content.scss +++ b/packages/block-editor/src/components/iframe/content.scss @@ -1,3 +1,7 @@ +.block-editor-iframe__body { + position: relative; +} + .block-editor-iframe__container { width: 100%; height: 100%; diff --git a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js index 25a3dc9dadfa96..8417dec1dd48ff 100644 --- a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js +++ b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useLayoutEffect, useMemo, useState } from '@wordpress/element'; -import { useDispatch, useRegistry } from '@wordpress/data'; +import { useRegistry } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -69,7 +69,6 @@ export default function useNestedSettingsUpdate( // Instead of adding a useSelect mapping here, please add to the useSelect // mapping in InnerBlocks! Every subscription impacts performance. - const { updateBlockListSettings } = useDispatch( blockEditorStore ); const registry = useRegistry(); // Implementors often pass a new array on every render, @@ -155,21 +154,16 @@ export default function useNestedSettingsUpdate( // we batch all the updatedBlockListSettings in a single "data" batch // which results in a single re-render. if ( ! pendingSettingsUpdates.get( registry ) ) { - pendingSettingsUpdates.set( registry, [] ); + pendingSettingsUpdates.set( registry, {} ); } - pendingSettingsUpdates - .get( registry ) - .push( [ clientId, newSettings ] ); + pendingSettingsUpdates.get( registry )[ clientId ] = newSettings; window.queueMicrotask( () => { - if ( pendingSettingsUpdates.get( registry )?.length ) { - registry.batch( () => { - pendingSettingsUpdates - .get( registry ) - .forEach( ( args ) => { - updateBlockListSettings( ...args ); - } ); - pendingSettingsUpdates.set( registry, [] ); - } ); + const settings = pendingSettingsUpdates.get( registry ); + if ( Object.keys( settings ).length ) { + const { updateBlockListSettings } = + registry.dispatch( blockEditorStore ); + updateBlockListSettings( settings ); + pendingSettingsUpdates.set( registry, {} ); } } ); }, [ @@ -183,7 +177,6 @@ export default function useNestedSettingsUpdate( __experimentalDirectInsert, captureToolbars, orientation, - updateBlockListSettings, layout, registry, ] ); diff --git a/packages/block-editor/src/components/inner-blocks/warning-max-depth-exceeded.native.js b/packages/block-editor/src/components/inner-blocks/warning-max-depth-exceeded.native.js index e363db4961c7c3..f759b0e519fd60 100644 --- a/packages/block-editor/src/components/inner-blocks/warning-max-depth-exceeded.native.js +++ b/packages/block-editor/src/components/inner-blocks/warning-max-depth-exceeded.native.js @@ -89,7 +89,7 @@ const WarningMaxDepthExceeded = ( { clientId } ) => { setShowDetails( true ) } > diff --git a/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js b/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js index d1021b639a5c57..709e005b587cea 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js +++ b/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js @@ -31,7 +31,7 @@ function PatternsListHeader( { filterValue, filteredBlockPatternsLength } ) { return ( { sprintf( 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 04f25ade4880d5..f91598fb7a61cc 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,10 +1,10 @@ /** * Internal dependencies */ - import { PatternCategoryPreviews } from './pattern-category-previews'; +import { useZoomOut } from '../../../hooks/use-zoom-out'; -export function PatternCategoryPreviewPanel( { +function PatternCategoryPreviewPanelInner( { rootClientId, onInsert, onHover, @@ -24,3 +24,17 @@ export function PatternCategoryPreviewPanel( { /> ); } + +function PatternCategoryPreviewPanelWithZoomOut( props ) { + useZoomOut(); + return ; +} + +export function PatternCategoryPreviewPanel( props ) { + // When the pattern panel is showing, we want to use zoom out mode + if ( window.__experimentalEnableZoomedOutPatternsTab ) { + return ; + } + + 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 9ee57ed7950a83..3703381b23a140 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 @@ -136,7 +136,12 @@ export function PatternCategoryPreviews( { > - + { category.label } diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js index 7f5abf0b8540ea..9e57f00b6b4661 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js @@ -113,7 +113,8 @@ export function PatternsFilter( { popoverProps={ { placement: 'right-end', } } - label="Filter patterns" + label={ __( 'Filter patterns' ) } + toggleProps={ { size: 'compact' } } icon={ } 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 57f66b6e4bb6ac..50a8b46b46427c 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.js @@ -3,7 +3,7 @@ */ import { __, _x } from '@wordpress/i18n'; import { useMemo, useEffect, forwardRef } from '@wordpress/element'; -import { pipe, useAsyncList } from '@wordpress/compose'; +import { useAsyncList } from '@wordpress/compose'; /** * Internal dependencies @@ -27,15 +27,15 @@ const MAX_SUGGESTED_ITEMS = 6; */ const EMPTY_ARRAY = []; -export function BlockTypesTab( - { rootClientId, onInsert, onHover, showMostUsedBlocks }, - ref -) { - const [ items, categories, collections, onSelectItem ] = useBlockTypesState( - rootClientId, - onInsert - ); - +export function BlockTypesTabPanel( { + items, + collections, + categories, + onSelectItem, + onHover, + showMostUsedBlocks, + className, +} ) { const suggestedItems = useMemo( () => { return orderBy( items, 'frecency', 'desc' ).slice( 0, @@ -47,24 +47,6 @@ export function BlockTypesTab( return items.filter( ( item ) => ! item.category ); }, [ items ] ); - const itemsPerCategory = useMemo( () => { - return pipe( - ( itemList ) => - itemList.filter( - ( item ) => item.category && item.category !== 'reusable' - ), - ( itemList ) => - itemList.reduce( ( acc, item ) => { - const { category } = item; - if ( ! acc[ category ] ) { - acc[ category ] = []; - } - acc[ category ].push( item ); - return acc; - }, {} ) - )( items ); - }, [ items ] ); - const itemsPerCollection = useMemo( () => { // Create a new Object to avoid mutating collection. const result = { ...collections }; @@ -101,14 +83,13 @@ export function BlockTypesTab( didRenderAllCategories ? collectionEntries : EMPTY_ARRAY ); - if ( ! items.length ) { - return ; - } - return ( - -
- { showMostUsedBlocks && !! suggestedItems.length && ( +
+ { showMostUsedBlocks && + // Only show the most used blocks if the total amount of block + // is larger than 1 row, otherwise it is not so useful. + items.length > 3 && + !! suggestedItems.length && ( ) } - { currentlyRenderedCategories.map( ( category ) => { - const categoryItems = itemsPerCategory[ category.slug ]; - if ( ! categoryItems || ! categoryItems.length ) { + { currentlyRenderedCategories.map( ( category ) => { + const categoryItems = items.filter( + ( item ) => item.category === category.slug + ); + if ( ! categoryItems || ! categoryItems.length ) { + return null; + } + return ( + + + + ); + } ) } + + { didRenderAllCategories && uncategorizedItems.length > 0 && ( + + + + ) } + + { currentlyRenderedCollections.map( + ( [ namespace, collection ] ) => { + const collectionItems = itemsPerCollection[ namespace ]; + if ( ! collectionItems || ! collectionItems.length ) { return null; } + return ( ); - } ) } + } + ) } +
+ ); +} - { didRenderAllCategories && uncategorizedItems.length > 0 && ( - - ; + } + + const itemsForCurrentRoot = []; + const itemsRemaining = []; + + for ( const item of items ) { + // Skip reusable blocks, they moved to the patterns tab. + if ( item.category === 'reusable' ) { + continue; + } + + if ( rootClientId && item.rootClientId === rootClientId ) { + itemsForCurrentRoot.push( item ); + } else { + itemsRemaining.push( item ); + } + } + + return ( + +
+ { !! itemsForCurrentRoot.length && ( + <> + - - ) } - - { currentlyRenderedCollections.map( - ( [ namespace, collection ] ) => { - const collectionItems = itemsPerCollection[ namespace ]; - if ( ! collectionItems || ! collectionItems.length ) { - return null; - } - - return ( - - - - ); - } + ) } +
); diff --git a/packages/block-editor/src/components/inserter/block-types-tab.native.js b/packages/block-editor/src/components/inserter/block-types-tab.native.js index e6108b95d60c37..e92efde21b1388 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.native.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.native.js @@ -18,7 +18,8 @@ const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ]; function BlockTypesTab( { onSelect, rootClientId, listProps } ) { const [ rawBlockTypes, , collections ] = useBlockTypesState( rootClientId, - onSelect + onSelect, + true ); const clipboardBlock = useClipboardBlock( rootClientId ); const filteredBlockTypes = filterInserterItems( rawBlockTypes ); diff --git a/packages/block-editor/src/components/inserter/category-tabs/index.js b/packages/block-editor/src/components/inserter/category-tabs/index.js index 47c9f0e051a66e..a379f4719556b2 100644 --- a/packages/block-editor/src/components/inserter/category-tabs/index.js +++ b/packages/block-editor/src/components/inserter/category-tabs/index.js @@ -27,7 +27,7 @@ function CategoryTabs( { className="block-editor-inserter__category-tabs" selectOnMove={ false } selectedTabId={ selectedCategory ? selectedCategory.name : null } - orientation={ 'vertical' } + orientation="vertical" onSelect={ ( categoryId ) => { // Pass the full category object onSelectCategory( diff --git a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js index 566d0476fbd0f5..8db23267eee8f4 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js +++ b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js @@ -8,26 +8,35 @@ import { parse, } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; /** * Internal dependencies */ import { store as blockEditorStore } from '../../../store'; +import { withRootClientIdOptionKey } from '../../../store/utils'; /** * Retrieves the block types inserter state. * * @param {string=} rootClientId Insertion's root client ID. * @param {Function} onInsert function called when inserter a list of blocks. + * @param {boolean} isQuick * @return {Array} Returns the block types state. (block types, categories, collections, onSelect handler) */ -const useBlockTypesState = ( rootClientId, onInsert ) => { +const useBlockTypesState = ( rootClientId, onInsert, isQuick ) => { + const options = useMemo( + () => ( { [ withRootClientIdOptionKey ]: ! isQuick } ), + [ isQuick ] + ); const [ items ] = useSelect( ( select ) => [ - select( blockEditorStore ).getInserterItems( rootClientId ), + select( blockEditorStore ).getInserterItems( + rootClientId, + options + ), ], - [ rootClientId ] + [ rootClientId, options ] ); const [ categories, collections ] = useSelect( ( select ) => { @@ -37,7 +46,14 @@ const useBlockTypesState = ( rootClientId, onInsert ) => { const onSelectItem = useCallback( ( - { name, initialAttributes, innerBlocks, syncStatus, content }, + { + name, + initialAttributes, + innerBlocks, + syncStatus, + content, + rootClientId: _rootClientId, + }, shouldFocusBlock ) => { const insertedBlock = @@ -51,7 +67,12 @@ const useBlockTypesState = ( rootClientId, onInsert ) => { createBlocksFromInnerBlocksTemplate( innerBlocks ) ); - onInsert( insertedBlock, undefined, shouldFocusBlock ); + onInsert( + insertedBlock, + undefined, + shouldFocusBlock, + _rootClientId + ); }, [ onInsert ] ); diff --git a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js index 0dae090578ab4f..24074ec5004565 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js +++ b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useDispatch, useSelect } from '@wordpress/data'; +import { useDispatch, useRegistry, useSelect } from '@wordpress/data'; import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { _n, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; @@ -13,6 +13,34 @@ import { useCallback } from '@wordpress/element'; import { store as blockEditorStore } from '../../../store'; import { unlock } from '../../../lock-unlock'; +function getIndex( { + destinationRootClientId, + destinationIndex, + rootClientId, + registry, +} ) { + if ( rootClientId === destinationRootClientId ) { + return destinationIndex; + } + const parents = [ + '', + ...registry + .select( blockEditorStore ) + .getBlockParents( destinationRootClientId ), + destinationRootClientId, + ]; + const parentIndex = parents.indexOf( rootClientId ); + if ( parentIndex !== -1 ) { + return ( + registry + .select( blockEditorStore ) + .getBlockIndex( parents[ parentIndex + 1 ] ) + 1 + ); + } + return registry.select( blockEditorStore ).getBlockOrder( rootClientId ) + .length; +} + /** * @typedef WPInserterConfig * @@ -42,6 +70,7 @@ function useInsertionPoint( { shouldFocusBlock = true, selectBlockOnInsert = true, } ) { + const registry = useRegistry(); const { getSelectedBlock } = useSelect( blockEditorStore ); const { destinationRootClientId, destinationIndex } = useSelect( ( select ) => { @@ -91,7 +120,7 @@ function useInsertionPoint( { } = unlock( useDispatch( blockEditorStore ) ); const onInsertBlocks = useCallback( - ( blocks, meta, shouldForceFocusBlock = false ) => { + ( blocks, meta, shouldForceFocusBlock = false, _rootClientId ) => { // When we are trying to move focus or select a new block on insert, we also // need to clear the last focus to avoid the focus being set to the wrong block // when tabbing back into the canvas if the block was added from outside the @@ -121,8 +150,17 @@ function useInsertionPoint( { } else { insertBlocks( blocks, - destinationIndex, - destinationRootClientId, + isAppender || _rootClientId === undefined + ? destinationIndex + : getIndex( { + destinationRootClientId, + destinationIndex, + rootClientId: _rootClientId, + registry, + } ), + isAppender || _rootClientId === undefined + ? destinationRootClientId + : _rootClientId, selectBlockOnInsert, shouldFocusBlock || shouldForceFocusBlock ? 0 : null, meta @@ -154,9 +192,17 @@ function useInsertionPoint( { ); const onToggleInsertionPoint = useCallback( - ( show ) => { - if ( show ) { - showInsertionPoint( destinationRootClientId, destinationIndex ); + ( item ) => { + if ( item?.hasOwnProperty( 'rootClientId' ) ) { + showInsertionPoint( + item.rootClientId, + getIndex( { + destinationRootClientId, + destinationIndex, + rootClientId: item.rootClientId, + registry, + } ) + ); } else { hideInsertionPoint(); } diff --git a/packages/block-editor/src/components/inserter/library.js b/packages/block-editor/src/components/inserter/library.js index 6be31f49b5fbe1..4e10a051996a9f 100644 --- a/packages/block-editor/src/components/inserter/library.js +++ b/packages/block-editor/src/components/inserter/library.js @@ -7,7 +7,7 @@ import { forwardRef } from '@wordpress/element'; /** * Internal dependencies */ -import InserterMenu from './menu'; +import { PrivateInserterMenu } from './menu'; import { store as blockEditorStore } from '../../store'; const noop = () => {}; @@ -23,7 +23,7 @@ function InserterLibrary( __experimentalInitialTab, __experimentalInitialCategory, __experimentalFilterValue, - __experimentalOnPatternCategorySelection, + onPatternCategorySelection, onSelect = noop, shouldFocusBlock = false, onClose, @@ -43,7 +43,7 @@ function InserterLibrary( ); return ( - + ); +} + +export default forwardRef( PublicInserterLibrary ); diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index fe9f4e60f0c861..6a4ac798b74900 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -31,7 +31,6 @@ import { MediaTab, MediaCategoryPanel } from './media-tab'; import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; import InserterTabs from './tabs'; -import { useZoomOut } from '../../hooks/use-zoom-out'; import { store as blockEditorStore } from '../../store'; const NOOP = () => {}; @@ -46,7 +45,7 @@ function InserterMenu( showMostUsedBlocks, __experimentalFilterValue = '', shouldFocusBlock = true, - __experimentalOnPatternCategorySelection = NOOP, + onPatternCategorySelection, onClose, __experimentalInitialTab, __experimentalInitialCategory, @@ -82,8 +81,13 @@ function InserterMenu( const blockTypesTabRef = useRef(); const onInsert = useCallback( - ( blocks, meta, shouldForceFocusBlock ) => { - onInsertBlocks( blocks, meta, shouldForceFocusBlock ); + ( blocks, meta, shouldForceFocusBlock, _rootClientId ) => { + onInsertBlocks( + blocks, + meta, + shouldForceFocusBlock, + _rootClientId + ); onSelect(); // Check for focus loss due to filtering blocks by selected block type @@ -112,7 +116,7 @@ function InserterMenu( const onHover = useCallback( ( item ) => { - onToggleInsertionPoint( !! item ); + onToggleInsertionPoint( item ); setHoveredItem( item ); }, [ onToggleInsertionPoint, setHoveredItem ] @@ -129,9 +133,9 @@ function InserterMenu( ( patternCategory, filter ) => { setSelectedPatternCategory( patternCategory ); setPatternFilter( filter ); - __experimentalOnPatternCategorySelection(); + onPatternCategorySelection?.(); }, - [ setSelectedPatternCategory, __experimentalOnPatternCategorySelection ] + [ setSelectedPatternCategory, onPatternCategorySelection ] ); const showPatternPanel = @@ -282,9 +286,6 @@ function InserterMenu( showMediaPanel, ] ); - // 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. if ( value !== 'patterns' ) { @@ -345,4 +346,16 @@ function InserterMenu( ); } -export default forwardRef( InserterMenu ); +export const PrivateInserterMenu = forwardRef( InserterMenu ); + +function PublicInserterMenu( props, ref ) { + return ( + + ); +} + +export default forwardRef( PublicInserterMenu ); diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 3405ac98b881cc..022957df952cea 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -44,7 +44,8 @@ export default function QuickInserter( { } ); const [ blockTypes ] = useBlockTypesState( destinationRootClientId, - onInsertBlocks + onInsertBlocks, + true ); const [ patterns ] = usePatternsState( @@ -126,6 +127,7 @@ export default function QuickInserter( { isDraggable={ false } prioritizePatterns={ prioritizePatterns } selectBlockOnInsert={ selectBlockOnInsert } + isQuick />
diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js index edd99609ea916c..9c001823745e6c 100644 --- a/packages/block-editor/src/components/inserter/search-results.js +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -50,6 +50,7 @@ function InserterSearchResults( { shouldFocusBlock = true, prioritizePatterns, selectBlockOnInsert, + isQuick, } ) { const debouncedSpeak = useDebounce( speak, 500 ); @@ -80,7 +81,7 @@ function InserterSearchResults( { blockTypeCategories, blockTypeCollections, onSelectBlockType, - ] = useBlockTypesState( destinationRootClientId, onInsertBlocks ); + ] = useBlockTypesState( destinationRootClientId, onInsertBlocks, isQuick ); const [ patterns, , onClickPattern ] = usePatternsState( onInsertBlocks, destinationRootClientId diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 604268fb1fe431..d3f4e4e14335c2 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -115,12 +115,25 @@ $block-inserter-tabs-height: 44px; flex-direction: column; overflow: hidden; + .block-editor-inserter__tablist-and-close-button { + border-bottom: $border-width solid $gray-300; + padding-right: $grid-unit-15; + display: flex; + justify-content: space-between; + } + + .block-editor-inserter__close-button { + /* stylelint-disable-next-line property-disallowed-list -- This should be refactored to avoid order if possible */ + order: 1; + align-self: center; + } + .block-editor-inserter__tablist { width: 100%; + margin-bottom: -$border-width; button[role="tab"] { flex-grow: 1; - margin-bottom: -$border-width; &[id$="reusable"] { flex-grow: inherit; // These are to align the `reusable` icon with the search icon. @@ -240,6 +253,10 @@ $block-inserter-tabs-height: 44px; } } +.block-editor-inserter__insertable-blocks-at-selection { + border-bottom: $border-width solid $gray-200; +} + .block-editor-inserter__media-tabs-container, .block-editor-inserter__block-patterns-tabs-container { padding: $grid-unit-20; @@ -316,27 +333,35 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__category-panel { background: $gray-100; - border-left: $border-width solid $gray-200; - border-right: $border-width solid $gray-200; + border-top: $border-width solid $gray-200; + box-shadow: $border-width $border-width 0 0 rgba($color: #000, $alpha: 0.133); // 0.133 = $gray-200 but with alpha. + outline: 1px solid transparent; // Shown for Windows 10 High Contrast mode. position: absolute; - top: 0; + top: -$border-width; left: 0; - height: 100%; + height: calc(100% + #{$border-width}); width: 100%; padding: 0 $grid-unit-20; display: flex; flex-direction: column; @include break-medium { + border-left: $border-width solid $gray-200; padding: 0; left: 100%; width: 300px; } + + .block-editor-inserter__media-list, + .block-editor-block-patterns-list { + padding: 0 $grid-unit-30 $grid-unit-20; + } } .block-editor-inserter__patterns-category-panel-header { - padding: 16px $grid-unit-30; + padding: $grid-unit-10 $grid-unit-30; } + .block-editor-inserter__patterns-category-no-results { margin-top: $grid-unit-30; } @@ -346,7 +371,6 @@ $block-inserter-tabs-height: 44px; overflow-y: auto; flex-grow: 1; height: 100%; - padding: $grid-unit-20 $grid-unit-30; } .block-editor-inserter__preview-content { @@ -507,8 +531,8 @@ $block-inserter-tabs-height: 44px; } } -.block-editor-inserter__patterns-category-panel-title { - font-size: calc(1.25 * 13px); +.components-heading.block-editor-inserter__patterns-category-panel-title { + font-weight: 500; } .block-editor-inserter__patterns-explore-button, @@ -540,7 +564,7 @@ $block-inserter-tabs-height: 44px; } .block-editor-inserter__media-panel-search { - padding: $grid-unit-20 $grid-unit-30 0; + padding: $grid-unit-20 $grid-unit-30 $grid-unit-10; // TODO: Consider using the Theme component to automatically adapt to a gray background. &:not(:focus-within) { --wp-components-color-background: #{$white}; @@ -552,7 +576,6 @@ $block-inserter-tabs-height: 44px; position: relative; cursor: pointer; margin-bottom: $grid-unit-30; - box-shadow: 0 15px 25px rgb(0 0 0 / 7%); &.is-placeholder { min-height: 100px; @@ -563,8 +586,8 @@ $block-inserter-tabs-height: 44px; } &.is-hovered { - .block-editor-inserter__media-list__item-preview { - box-shadow: 0 0 0 2px $gray-900, 0 15px 25px rgb(0 0 0 / 7%); + .block-editor-inserter__media-list__item-preview > * { + outline-color: rgba($black, 0.3); } .block-editor-inserter__media-list__item-preview-options > button { @@ -611,6 +634,9 @@ $block-inserter-tabs-height: 44px; > * { margin: 0 auto; max-width: 100%; + outline: $border-width solid rgba($black, 0.1); + outline-offset: -$border-width; + border-radius: $radius-block-ui; } .block-editor-inserter__media-list__item-preview-spinner { @@ -625,11 +651,12 @@ $block-inserter-tabs-height: 44px; } } - &: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; + &:focus .block-editor-inserter__media-list__item-preview > * { + outline-color: var(--wp-admin-theme-color); + outline-width: var(--wp-admin-border-width-focus); + outline-offset: calc((-1 * var(--wp-admin-border-width-focus))); + transition: outline 0.1s linear; + @include reduce-motion("transition"); } } diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index d3ce5d80993d3f..b46e4bfdaf0148 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -39,9 +39,9 @@ function InserterTabs( { onSelect, children, onClose, selectedTab }, ref ) { return (
-
+
} /> ) ) } diff --git a/packages/block-editor/src/components/inspector-controls-tabs/style.scss b/packages/block-editor/src/components/inspector-controls-tabs/style.scss index f25e89903a6efd..70614f56dbcb3b 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/style.scss +++ b/packages/block-editor/src/components/inspector-controls-tabs/style.scss @@ -1,15 +1,7 @@ .show-icon-labels { .block-editor-block-inspector__tabs [role="tablist"] { - .components-button.has-icon { - // Hide the button icons when labels are set to display... - svg { - display: none; - } - // ... and display labels. - // Uses ::before as ::after is already used for active tab styling. - &::before { - content: attr(aria-label); - } + .components-button { + justify-content: center; } } } diff --git a/packages/block-editor/src/components/inspector-controls/fill.native.js b/packages/block-editor/src/components/inspector-controls/fill.native.js index 98b6698721e1ce..69da92dd42b378 100644 --- a/packages/block-editor/src/components/inspector-controls/fill.native.js +++ b/packages/block-editor/src/components/inspector-controls/fill.native.js @@ -52,11 +52,9 @@ export default function InspectorControlsFill( { return ( <> - { - - { () => { children } } - - } + + { () => { children } } + { Children.count( children ) > 0 && } diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 053839c86996de..576556519fb2f4 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -220,7 +220,6 @@ function LinkControl( { // because otherwise using the keyboard to select text // *within* the link format is not possible. if ( isMounting.current ) { - isMounting.current = false; return; } @@ -238,6 +237,16 @@ function LinkControl( { isEndingEditWithFocus.current = false; }, [ isEditingLink, isCreatingPage ] ); + // The component mounting reference is maintained separately + // to correctly reset values in `StrictMode`. + useEffect( () => { + isMounting.current = false; + + return () => { + isMounting.current = true; + }; + }, [] ); + const hasLinkValue = value?.url?.trim()?.length > 0; /** diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js index 867b69356eb9d9..d06c9971c680bf 100644 --- a/packages/block-editor/src/components/link-control/link-preview.js +++ b/packages/block-editor/src/components/link-control/link-preview.js @@ -27,6 +27,20 @@ import { ViewerSlot } from './viewer-slot'; import useRichUrlData from './use-rich-url-data'; +/** + * Filters the title for display. Removes the protocol and www prefix. + * + * @param {string} title The title to be filtered. + * + * @return {string} The filtered title. + */ +function filterTitleForDisplay( title ) { + // Derived from `filterURLForDisplay` in `@wordpress/url`. + return title + .replace( /^[a-z\-.\+]+[0-9]*:(\/\/)?/i, '' ) + .replace( /^www\./i, '' ); +} + export default function LinkPreview( { value, onEditClick, @@ -59,6 +73,9 @@ export default function LinkPreview( { ! isEmptyURL && stripHTML( richData?.title || value?.title || displayURL ); + const isUrlRedundant = + ! value?.url || filterTitleForDisplay( displayTitle ) === displayURL; + let icon; if ( richData?.icon ) { @@ -112,7 +129,7 @@ export default function LinkPreview( { { displayTitle } - { value?.url && displayTitle !== displayURL && ( + { ! isUrlRedundant && ( { displayURL } @@ -149,6 +166,7 @@ export default function LinkPreview( { isEmptyURL || showIconLabels ? '' : ': ' + value.url ) } ref={ ref } + __experimentalIsFocusable disabled={ isEmptyURL } size="compact" /> diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 238b2d6b3acc7c..78730463389e26 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -125,7 +125,7 @@ const LinkControlSearchInput = forwardRef( className={ className } value={ value } onChange={ onInputChange } - placeholder={ placeholder ?? __( 'Search or type url' ) } + placeholder={ placeholder ?? __( 'Search or type URL' ) } __experimentalRenderSuggestions={ showSuggestions ? handleRenderSuggestions : null } diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 91428274a99140..c0cf8a3bc2afe9 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -36,13 +36,19 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__search-item-top { - gap: $grid-unit-10; + gap: $grid-unit-05; + flex-wrap: wrap; .components-button.has-icon { - min-width: inherit; - width: min-content; + width: auto; + padding: $grid-unit-05; } } + + .is-preview .block-editor-link-control__search-item-header { + min-width: 100%; + margin-right: 0; + } } } 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 f6abaee9258d10..91bfbd7eddaa03 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -77,7 +77,7 @@ const ListViewBlockContents = forwardRef( { ( { draggable, onDragStart, onDragEnd } ) => ( { showBlock && ( diff --git a/packages/block-editor/src/components/list-view/use-clipboard-handler.js b/packages/block-editor/src/components/list-view/use-clipboard-handler.js index dd3ac65ac79d24..e7c3026e70f090 100644 --- a/packages/block-editor/src/components/list-view/use-clipboard-handler.js +++ b/packages/block-editor/src/components/list-view/use-clipboard-handler.js @@ -112,12 +112,7 @@ export default function useClipboardHandler( { selectBlock } ) { if ( event.type === 'cut' ) { // Don't update the selection if the blocks cannot be deleted. - if ( - ! canRemoveBlocks( - selectedBlockClientIds, - firstBlockRootClientId - ) - ) { + if ( ! canRemoveBlocks( selectedBlockClientIds ) ) { return; } diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 3391f72916167a..7fb46d3ca9d56a 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -452,7 +452,7 @@ export function MediaPlaceholder( { multiple={ multiple } onSelect={ onSelect } allowedTypes={ allowedTypes } - mode={ 'browse' } + mode="browse" value={ Array.isArray( value ) ? value.map( ( { id } ) => id ) diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index e597b65d63b865..bbd9cbad73a4a1 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -164,7 +164,7 @@ function MediaPlaceholder( props ) { activeOpacity={ 0.5 } accessibilityLabel={ accessibilityLabel } style={ buttonStyles } - accessibilityRole={ 'button' } + accessibilityRole="button" accessibilityHint={ accessibilityHint } hitSlop={ hitSlop } onPress={ onButtonPress( open ) } @@ -181,7 +181,7 @@ function MediaPlaceholder( props ) { activeOpacity={ 0.5 } accessibilityLabel={ accessibilityLabel } style={ styles[ 'media-placeholder__appender' ] } - accessibilityRole={ 'button' } + accessibilityRole="button" accessibilityHint={ accessibilityHint } hitSlop={ hitSlop } onPress={ onButtonPress( open ) } diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index 300c108a70cf1a..4e9cc9784554f5 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -9,7 +9,6 @@ import { cloneBlock } from '@wordpress/blocks'; * Internal dependencies */ import { store as blockEditorStore } from '../../store'; -import { undoIgnoreBlocks } from '../../store/undo-ignore'; const noop = () => {}; @@ -274,10 +273,6 @@ export default function useBlockSync( { const updateParent = isPersistent ? onChangeRef.current : onInputRef.current; - const undoIgnore = undoIgnoreBlocks.has( blocks ); - if ( undoIgnore ) { - undoIgnoreBlocks.delete( blocks ); - } updateParent( blocks, { selection: { selectionStart: getSelectionStart(), @@ -285,7 +280,6 @@ export default function useBlockSync( { initialPosition: getSelectedBlocksInitialCaretPosition(), }, - undoIgnore, } ); } previousAreBlocksDifferent = areBlocksDifferent; diff --git a/packages/block-editor/src/components/provider/with-registry-provider.js b/packages/block-editor/src/components/provider/with-registry-provider.js index 5c00da6e93eabc..582a9bd547f20e 100644 --- a/packages/block-editor/src/components/provider/with-registry-provider.js +++ b/packages/block-editor/src/components/provider/with-registry-provider.js @@ -1,12 +1,8 @@ /** * WordPress dependencies */ -import { useState, useEffect } from '@wordpress/element'; -import { - withRegistry, - createRegistry, - RegistryProvider, -} from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { useRegistry, createRegistry, RegistryProvider } from '@wordpress/data'; import { createHigherOrderComponent } from '@wordpress/compose'; /** @@ -15,41 +11,40 @@ import { createHigherOrderComponent } from '@wordpress/compose'; import { storeConfig } from '../../store'; import { STORE_NAME as blockEditorStoreName } from '../../store/constants'; -const withRegistryProvider = createHigherOrderComponent( - ( WrappedComponent ) => { - return withRegistry( - ( { useSubRegistry = true, registry, ...props } ) => { - if ( ! useSubRegistry ) { - return ( - - ); - } - - const [ subRegistry, setSubRegistry ] = useState( null ); - useEffect( () => { - const newRegistry = createRegistry( {}, registry ); - newRegistry.registerStore( - blockEditorStoreName, - storeConfig - ); - setSubRegistry( newRegistry ); - }, [ registry ] ); +function getSubRegistry( subRegistries, registry, useSubRegistry ) { + if ( ! useSubRegistry ) { + return registry; + } + let subRegistry = subRegistries.get( registry ); + if ( ! subRegistry ) { + subRegistry = createRegistry( {}, registry ); + subRegistry.registerStore( blockEditorStoreName, storeConfig ); + subRegistries.set( registry, subRegistry ); + } + return subRegistry; +} - if ( ! subRegistry ) { - return null; - } +const withRegistryProvider = createHigherOrderComponent( + ( WrappedComponent ) => + ( { useSubRegistry = true, ...props } ) => { + const registry = useRegistry(); + const [ subRegistries ] = useState( () => new WeakMap() ); + const subRegistry = getSubRegistry( + subRegistries, + registry, + useSubRegistry + ); - return ( - - - - ); + if ( subRegistry === registry ) { + return ; } - ); - }, + + return ( + + + + ); + }, 'withRegistryProvider' ); diff --git a/packages/block-editor/src/components/publish-date-time-picker/index.js b/packages/block-editor/src/components/publish-date-time-picker/index.js index 418006cf854c14..eeaa5b2daad6fa 100644 --- a/packages/block-editor/src/components/publish-date-time-picker/index.js +++ b/packages/block-editor/src/components/publish-date-time-picker/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { DateTimePicker } from '@wordpress/components'; +import { DateTimePicker, TimePicker } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { forwardRef } from '@wordpress/element'; import { getSettings } from '@wordpress/date'; @@ -11,29 +11,57 @@ import { getSettings } from '@wordpress/date'; */ import InspectorPopoverHeader from '../inspector-popover-header'; -function PublishDateTimePicker( - { onClose, onChange, ...additionalProps }, +export function PublishDateTimePicker( + { + onClose, + onChange, + showPopoverHeaderActions, + isCompact, + currentDate, + ...additionalProps + }, ref ) { + const datePickerProps = { + startOfWeek: getSettings().l10n.startOfWeek, + onChange, + currentDate: isCompact ? undefined : currentDate, + currentTime: isCompact ? currentDate : undefined, + ...additionalProps, + }; + const DatePickerComponent = isCompact ? TimePicker : DateTimePicker; return (
onChange?.( null ), - }, - ] } + actions={ + showPopoverHeaderActions + ? [ + { + label: __( 'Now' ), + onClick: () => onChange?.( null ), + }, + ] + : undefined + } onClose={ onClose } /> - +
); } -export default forwardRef( PublishDateTimePicker ); +export const PrivatePublishDateTimePicker = forwardRef( PublishDateTimePicker ); + +function PublicPublishDateTimePicker( props, ref ) { + return ( + + ); +} + +export default forwardRef( PublicPublishDateTimePicker ); diff --git a/packages/block-editor/src/components/resolution-tool/index.js b/packages/block-editor/src/components/resolution-tool/index.js index 71c7e508ca3edb..42fea6e8655a8e 100644 --- a/packages/block-editor/src/components/resolution-tool/index.js +++ b/packages/block-editor/src/components/resolution-tool/index.js @@ -49,7 +49,7 @@ export default function ResolutionTool( { options={ options } onChange={ onChange } help={ __( 'Select the size of the source image.' ) } - size={ '__unstable-large' } + size="__unstable-large" /> ); diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 2a0afcf24f0ddc..cc22c9b804130a 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -11,6 +11,7 @@ import { useCallback, forwardRef, createContext, + useContext, } from '@wordpress/element'; import { useDispatch, useRegistry, useSelect } from '@wordpress/data'; import { useMergeRefs, useInstanceId } from '@wordpress/compose'; @@ -39,6 +40,7 @@ import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; import { unlock } from '../../lock-unlock'; import { canBindBlock } from '../../hooks/use-bindings-attributes'; +import BlockContext from '../block-context'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -121,6 +123,7 @@ export function RichTextWrapper( const context = useBlockEditContext(); const { clientId, isSelected: isBlockSelected, name: blockName } = context; const blockBindings = context[ blockBindingsKey ]; + const blockContext = useContext( BlockContext ); const selector = ( select ) => { // Avoid subscribing to the block editor store if the block is not // selected. @@ -170,7 +173,7 @@ export function RichTextWrapper( const { getBlockBindingsSource } = unlock( select( blocksStore ) ); - for ( const [ attribute, args ] of Object.entries( + for ( const [ attribute, binding ] of Object.entries( blockBindings ) ) { if ( @@ -180,13 +183,16 @@ export function RichTextWrapper( break; } - // If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it. + // If the source is not defined, or if its value of `canUserEditValue` is `false`, disable it. const blockBindingsSource = getBlockBindingsSource( - args.source + binding.source ); if ( - ! blockBindingsSource || - blockBindingsSource.lockAttributesEditing() + ! blockBindingsSource?.canUserEditValue( { + select, + context: blockContext, + args: binding.args, + } ) ) { _disableBoundBlocks = true; break; diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 2697f5fdf154b6..9390c71bdcf0b5 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -80,6 +80,7 @@ export function RichTextWrapper( unstableOnFocus, __unstableAllowPrefixTransformations, // Native props. + __unstableUseSplitSelection, __unstableMobileNoFocusOnMount, deleteEnter, placeholderTextColor, @@ -178,6 +179,7 @@ export function RichTextWrapper( exitFormattedText, selectionChange, __unstableMarkAutomaticChange, + __unstableSplitSelection, clearSelectedBlock, } = useDispatch( blockEditorStore ); const adjustedAllowedFormats = getAllowedFormats( { @@ -345,6 +347,8 @@ export function RichTextWrapper( } } else if ( canSplit ) { splitValue( value ); + } else if ( __unstableUseSplitSelection ) { + __unstableSplitSelection(); } else if ( canSplitAtEnd ) { onSplitAtEnd(); } else if ( diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 26d39a0c6058b4..4eeaabe6d790aa 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -402,7 +402,8 @@ export class RichText extends Component { this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; const value = this.createRecord(); - const { start, end, text } = value; + const { start, end, text, activeFormats } = value; + const hasActiveFormats = activeFormats && !! activeFormats.length; let newValue; // Always handle full content deletion ourselves. @@ -415,15 +416,17 @@ export class RichText extends Component { // Only process delete if the key press occurs at an uncollapsed edge. if ( - ! onDelete || ! isCollapsed( value ) || + hasActiveFormats || ( isReverse && start !== 0 ) || ( ! isReverse && end !== text.length ) ) { return; } - onDelete( { isReverse, value } ); + if ( onDelete ) { + onDelete( { isReverse, value } ); + } event.preventDefault(); this.lastAztecEventType = 'input'; diff --git a/packages/block-editor/src/components/rich-text/native/test/index.native.js b/packages/block-editor/src/components/rich-text/native/test/index.native.js index 8197952a116ddf..26b6d6c5358ec5 100644 --- a/packages/block-editor/src/components/rich-text/native/test/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/test/index.native.js @@ -151,7 +151,7 @@ describe( '', () => { const expectedFontSize = 16; // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = getByLabelText( 'editor' ).props.fontSize; @@ -164,8 +164,8 @@ describe( '', () => { // Act. const { getByLabelText } = render( ); // Assert. @@ -179,7 +179,7 @@ describe( '', () => { // Act. const { getByLabelText } = render( ); @@ -195,7 +195,7 @@ describe( '', () => { mockGlobalSettings( { fontSize: 'min(2em, 3em)' } ); // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = getByLabelText( 'editor' ).props.fontSize; @@ -209,7 +209,7 @@ describe( '', () => { mockGlobalSettings( { fontSize: 'min(2em, 3em)' } ); // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = getByLabelText( 'editor' ).props.fontSize; @@ -224,7 +224,7 @@ describe( '', () => { mockGlobalSettings( { fontSize: unit } ); // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = @@ -241,9 +241,9 @@ describe( '', () => { // Act. const { getByLabelText } = render( ); @@ -260,7 +260,7 @@ describe( '', () => { // Act. const { getByLabelText } = render( @@ -276,7 +276,7 @@ describe( '', () => { Dimensions.set( { window: { ...window, width: 300 } } ); // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = getByLabelText( 'editor' ).props.fontSize; @@ -289,7 +289,7 @@ describe( '', () => { Dimensions.set( { window: { ...window, height: 300 } } ); // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = getByLabelText( 'editor' ).props.fontSize; @@ -335,7 +335,7 @@ describe( '', () => { const style = { lineHeight: 0.2 }; // Act. const { getByLabelText } = render( - + ); // Assert. const actualFontSize = getByLabelText( 'editor' ).props.lineHeight; diff --git a/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js b/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js index e75f3519a1ca6c..d822dd200b626d 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js +++ b/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js @@ -1,28 +1,63 @@ /** * WordPress dependencies */ +import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { useSettings } from '../../use-settings'; +import { RANGE_CONTROL_MAX_SIZE } from '../utils'; + +const EMPTY_ARRAY = []; + +const compare = new Intl.Collator( 'und', { numeric: true } ).compare; export default function useSpacingSizes() { - const spacingSizes = [ { name: 0, slug: '0', size: 0 } ]; - - const [ settingsSizes ] = useSettings( 'spacing.spacingSizes' ); - if ( settingsSizes ) { - spacingSizes.push( ...settingsSizes ); - } - - if ( spacingSizes.length > 8 ) { - spacingSizes.unshift( { - name: __( 'Default' ), - slug: 'default', - size: undefined, - } ); - } - - return spacingSizes; + const [ + customSpacingSizes, + themeSpacingSizes, + defaultSpacingSizes, + defaultSpacingSizesEnabled, + ] = useSettings( + 'spacing.spacingSizes.custom', + 'spacing.spacingSizes.theme', + 'spacing.spacingSizes.default', + 'spacing.defaultSpacingSizes' + ); + + const customSizes = customSpacingSizes ?? EMPTY_ARRAY; + + const themeSizes = themeSpacingSizes ?? EMPTY_ARRAY; + + const defaultSizes = + defaultSpacingSizes && defaultSpacingSizesEnabled !== false + ? defaultSpacingSizes + : EMPTY_ARRAY; + + return useMemo( () => { + const sizes = [ + { name: __( 'None' ), slug: '0', size: 0 }, + ...customSizes, + ...themeSizes, + ...defaultSizes, + ]; + + // Using numeric slugs opts-in to sorting by slug. + if ( sizes.every( ( { slug } ) => /^[0-9]/.test( slug ) ) ) { + sizes.sort( ( a, b ) => compare( a.slug, b.slug ) ); + } + + return sizes.length > RANGE_CONTROL_MAX_SIZE + ? [ + { + name: __( 'Default' ), + slug: 'default', + size: undefined, + }, + ...sizes, + ] + : sizes; + }, [ customSizes, themeSizes, defaultSizes ] ); } 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 6feb583c29cdb2..58a81d8b130a3e 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 @@ -23,6 +23,7 @@ import { settings } from '@wordpress/icons'; import { useSettings } from '../../use-settings'; import { store as blockEditorStore } from '../../../store'; import { + RANGE_CONTROL_MAX_SIZE, ALL_SIDES, LABELS, getSliderValueFromPreset, @@ -79,7 +80,7 @@ export default function SpacingInputControl( { value = getPresetValueFromCustomValue( value, spacingSizes ); let selectListSizes = spacingSizes; - const showRangeControl = spacingSizes.length <= 8; + const showRangeControl = spacingSizes.length <= RANGE_CONTROL_MAX_SIZE; const disableCustomSpacingSizes = useSelect( ( select ) => { const editorSettings = select( blockEditorStore ).getSettings(); @@ -230,7 +231,7 @@ export default function SpacingInputControl( { label={ ariaLabel } hideLabelFromVision className="spacing-sizes-control__custom-value-input" - size={ '__unstable-large' } + size="__unstable-large" onDragStart={ () => { if ( value?.charAt( 0 ) === '-' ) { setMinValue( 0 ); @@ -312,7 +313,7 @@ export default function SpacingInputControl( { options={ options } label={ ariaLabel } hideLabelFromVision - size={ '__unstable-large' } + size="__unstable-large" onMouseOver={ onMouseOver } onMouseOut={ onMouseOut } onFocus={ onMouseOver } diff --git a/packages/block-editor/src/components/spacing-sizes-control/utils.js b/packages/block-editor/src/components/spacing-sizes-control/utils.js index 32f0dbc59ac466..91c5a91934f6e3 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/utils.js +++ b/packages/block-editor/src/components/spacing-sizes-control/utils.js @@ -12,6 +12,8 @@ import { sidesVertical, } from '@wordpress/icons'; +export const RANGE_CONTROL_MAX_SIZE = 8; + export const ALL_SIDES = [ 'top', 'right', 'bottom', 'left' ]; export const DEFAULT_VALUES = { diff --git a/packages/block-editor/src/components/use-block-commands/index.js b/packages/block-editor/src/components/use-block-commands/index.js index 1b3a1d2215f818..8a09999ecdff3c 100644 --- a/packages/block-editor/src/components/use-block-commands/index.js +++ b/packages/block-editor/src/components/use-block-commands/index.js @@ -65,7 +65,7 @@ export const useTransformCommands = () => { selectedBlocks, rootClientId ), - canRemove: canRemoveBlocks( selectedBlockClientIds, rootClientId ), + canRemove: canRemoveBlocks( selectedBlockClientIds ), invalidSelection: false, }; }, [] ); @@ -150,8 +150,7 @@ const useActionsCommands = () => { const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); const canMove = - canMoveBlocks( clientIds, rootClientId ) && - getBlockCount( rootClientId ) !== 1; + canMoveBlocks( clientIds ) && getBlockCount( rootClientId ) !== 1; const commands = []; @@ -260,7 +259,7 @@ const useQuickActionsCommands = () => { canInsertBlockType( block.name, rootClientId ) ); } ); - const canRemove = canRemoveBlocks( clientIds, rootClientId ); + const canRemove = canRemoveBlocks( clientIds ); const commands = []; diff --git a/packages/block-editor/src/components/video-player/index.native.js b/packages/block-editor/src/components/video-player/index.native.js index a409c1d4ad99fc..3c9de8758579b6 100644 --- a/packages/block-editor/src/components/video-player/index.native.js +++ b/packages/block-editor/src/components/video-player/index.native.js @@ -99,7 +99,7 @@ class Video extends Component { // So we are setting controls=false and adding a play button that // will trigger presentFullscreenPlayer() controls={ false } - ignoreSilentSwitch={ 'ignore' } + ignoreSilentSwitch="ignore" paused={ ! isFullScreen } onLayout={ this.onVideoLayout } onFullscreenPlayerWillPresent={ () => { diff --git a/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js b/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js index a6d5c61b8b5c8f..bed07b183cc0cc 100644 --- a/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js +++ b/packages/block-editor/src/components/writing-flow/use-clipboard-handler.js @@ -6,6 +6,7 @@ import { findTransform, getBlockTransforms, hasBlockSupport, + switchToBlockType, } from '@wordpress/blocks'; import { documentHasSelection, @@ -60,6 +61,25 @@ export default function useClipboardHandler() { return; } + // Let native copy/paste behaviour take over in input fields. + // But always handle multiple selected blocks. + if ( ! hasMultiSelection() ) { + const { target } = event; + const { ownerDocument } = target; + // If copying, only consider actual text selection as selection. + // Otherwise, any focus on an input field is considered. + const hasSelection = + event.type === 'copy' || event.type === 'cut' + ? documentHasUncollapsedSelection( ownerDocument ) + : documentHasSelection( ownerDocument ) && + ! ownerDocument.activeElement.isContentEditable; + + // Let native copy behaviour take over in input fields. + if ( hasSelection ) { + return; + } + } + const { activeElement } = event.target.ownerDocument; if ( ! node.contains( activeElement ) ) { @@ -72,22 +92,6 @@ export default function useClipboardHandler() { const expandSelectionIsNeeded = ! shouldHandleWholeBlocks && ! isSelectionMergeable; if ( event.type === 'copy' || event.type === 'cut' ) { - if ( ! hasMultiSelection() ) { - const { target } = event; - const { ownerDocument } = target; - // If copying, only consider actual text selection as selection. - // Otherwise, any focus on an input field is considered. - const hasSelection = - event.type === 'copy' || event.type === 'cut' - ? documentHasUncollapsedSelection( ownerDocument ) - : documentHasSelection( ownerDocument ); - - // Let native copy behaviour take over in input fields. - if ( hasSelection ) { - return; - } - } - event.preventDefault(); if ( selectedBlockClientIds.length === 1 ) { @@ -205,15 +209,36 @@ export default function useClipboardHandler() { firstSelectedClientId ); - if ( - ! blocks.every( ( block ) => - canInsertBlockType( block.name, rootClientId ) - ) - ) { - return; + const newBlocks = []; + + for ( const block of blocks ) { + if ( canInsertBlockType( block.name, rootClientId ) ) { + newBlocks.push( block ); + } else { + // If a block cannot be inserted in a root block, try + // converting it to that root block type and insert the + // inner blocks. + // Example: paragraphs cannot be inserted into a list, + // so convert the paragraphs to a list for list items. + const rootBlockName = getBlockName( rootClientId ); + const switchedBlocks = + block.name !== rootBlockName + ? switchToBlockType( block, rootBlockName ) + : [ block ]; + + if ( ! switchedBlocks ) { + return; + } + + for ( const switchedBlock of switchedBlocks ) { + for ( const innerBlock of switchedBlock.innerBlocks ) { + newBlocks.push( innerBlock ); + } + } + } } - __unstableSplitSelection( blocks ); + __unstableSplitSelection( newBlocks ); event.preventDefault(); } } diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js new file mode 100644 index 00000000000000..c61f586575a530 --- /dev/null +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -0,0 +1,98 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { + BaseControl, + PanelBody, + __experimentalHStack as HStack, + __experimentalItemGroup as ItemGroup, + __experimentalItem as Item, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { canBindAttribute } from '../hooks/use-bindings-attributes'; +import { unlock } from '../lock-unlock'; +import InspectorControls from '../components/inspector-controls'; + +export const BlockBindingsPanel = ( { name, metadata } ) => { + const { bindings } = metadata || {}; + const { sources } = useSelect( ( select ) => { + const _sources = unlock( + select( blocksStore ) + ).getAllBlockBindingsSources(); + + return { + sources: _sources, + }; + }, [] ); + + if ( ! bindings ) { + return null; + } + + // Don't show not allowed attributes. + // Don't show the bindings connected to pattern overrides in the inspectors panel. + // TODO: Explore if this should be abstracted to let other sources decide. + const filteredBindings = { ...bindings }; + Object.keys( filteredBindings ).forEach( ( key ) => { + if ( + ! canBindAttribute( name, key ) || + filteredBindings[ key ].source === 'core/pattern-overrides' + ) { + delete filteredBindings[ key ]; + } + } ); + + if ( Object.keys( filteredBindings ).length === 0 ) { + return null; + } + + return ( + + + + + { Object.keys( filteredBindings ).map( ( key ) => { + return ( + + + { key } + + { sources[ + filteredBindings[ key ].source + ] + ? sources[ + filteredBindings[ key ] + .source + ].label + : filteredBindings[ key ] + .source } + + + + ); + } ) } + + + + + ); +}; + +export default { + edit: BlockBindingsPanel, + attributeKeys: [ 'metadata' ], + hasSupport() { + return true; + }, +}; diff --git a/packages/block-editor/src/hooks/block-bindings.scss b/packages/block-editor/src/hooks/block-bindings.scss new file mode 100644 index 00000000000000..fd46674ad11426 --- /dev/null +++ b/packages/block-editor/src/hooks/block-bindings.scss @@ -0,0 +1,3 @@ +.components-panel__block-bindings-panel .components-item__block-bindings-source { + color: $gray-700; +} diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js new file mode 100644 index 00000000000000..311997d46f0ada --- /dev/null +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -0,0 +1,156 @@ +/** + * WordPress dependencies + */ +import { getBlockTypes, store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { useContext, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { + GlobalStylesContext, + toStyles, + getBlockSelectors, +} from '../components/global-styles'; +import { useStyleOverride } from './utils'; +import { store as blockEditorStore } from '../store'; +import { globalStylesDataKey } from '../store/private-keys'; + +/** + * Get the first block style variation that has been registered from the class string. + * + * @param {string} className CSS class string for a block. + * @param {Array} registeredStyles Currently registered block styles. + * + * @return {string|null} The name of the first registered variation. + */ +function getVariationNameFromClass( className, registeredStyles = [] ) { + // The global flag affects how capturing groups work in JS. So the regex + // below will only return full CSS classes not just the variation name. + const matches = className?.match( /\bis-style-(?!default)(\S+)\b/g ); + + if ( ! matches ) { + return null; + } + + for ( const variationClass of matches ) { + const variation = variationClass.substring( 9 ); // Remove 'is-style-' prefix. + if ( registeredStyles.some( ( style ) => style.name === variation ) ) { + return variation; + } + } + return null; +} + +function useBlockStyleVariation( name, variation, clientId ) { + // Prefer global styles data in GlobalStylesContext, which are available + // if in the site editor. Otherwise fall back to whatever is in the + // editor settings and available in the post editor. + const { merged: mergedConfig } = useContext( GlobalStylesContext ); + const { globalSettings, globalStyles } = useSelect( ( select ) => { + const settings = select( blockEditorStore ).getSettings(); + return { + globalSettings: settings.__experimentalFeatures, + globalStyles: settings[ globalStylesDataKey ], + }; + }, [] ); + + return useMemo( () => { + const styles = mergedConfig?.styles ?? globalStyles; + const variationStyles = + styles?.blocks?.[ name ]?.variations?.[ variation ]; + + return { + settings: mergedConfig?.settings ?? globalSettings, + // The variation style data is all that is needed to generate + // the styles for the current application to a block. The variation + // name is updated to match the instance specific class name. + styles: { + blocks: { + [ name ]: { + variations: { + [ `${ variation }-${ clientId }` ]: variationStyles, + }, + }, + }, + }, + }; + }, [ + mergedConfig, + globalSettings, + globalStyles, + variation, + clientId, + name, + ] ); +} + +// Rather than leveraging `useInstanceId` here, the `clientId` is used. +// This is so that the variation style override's ID is predictable +// when the order of applied style variations changes. +function useBlockProps( { name, className, clientId } ) { + const { getBlockStyles } = useSelect( blocksStore ); + + const registeredStyles = getBlockStyles( name ); + const variation = getVariationNameFromClass( className, registeredStyles ); + const variationClass = `is-style-${ variation }-${ clientId }`; + + const { settings, styles } = useBlockStyleVariation( + name, + variation, + clientId + ); + + const variationStyles = useMemo( () => { + if ( ! variation ) { + return; + } + + const variationConfig = { settings, styles }; + const blockSelectors = getBlockSelectors( + getBlockTypes(), + getBlockStyles, + clientId + ); + const hasBlockGapSupport = false; + const hasFallbackGapSupport = true; + const disableLayoutStyles = true; + const isTemplate = true; + + return toStyles( + variationConfig, + blockSelectors, + hasBlockGapSupport, + hasFallbackGapSupport, + disableLayoutStyles, + isTemplate, + { + blockGap: false, + blockStyles: true, + layoutStyles: false, + marginReset: false, + presets: false, + rootPadding: false, + } + ); + }, [ variation, settings, styles, getBlockStyles, clientId ] ); + + useStyleOverride( { + id: `variation-${ clientId }`, + css: variationStyles, + __unstableType: 'variation', + // The clientId will be stored with the override and used to ensure + // the order of overrides matches the order of blocks so that the + // correct CSS cascade is maintained. + clientId, + } ); + + return variation ? { className: variationClass } : {}; +} + +export default { + hasSupport: () => true, + attributeKeys: [ 'className' ], + useBlockProps, +}; diff --git a/packages/block-editor/src/hooks/font-family.js b/packages/block-editor/src/hooks/font-family.js index db6515ef1c2fe0..ba9a66a8bcf04f 100644 --- a/packages/block-editor/src/hooks/font-family.js +++ b/packages/block-editor/src/hooks/font-family.js @@ -14,6 +14,7 @@ import { TYPOGRAPHY_SUPPORT_KEY } from './typography'; import { unlock } from '../lock-unlock'; export const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; +const { kebabCase } = unlock( componentsPrivateApis ); /** * Filters registered block settings, extending attributes to include @@ -68,7 +69,6 @@ function addSaveProps( props, blockType, attributes ) { // Use TokenList to dedupe classes. const classes = new TokenList( props.className ); - const { kebabCase } = unlock( componentsPrivateApis ); classes.add( `has-${ kebabCase( attributes?.fontFamily ) }-font-family` ); const newClassName = classes.value; props.className = newClassName ? newClassName : undefined; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index ec5cf29b49c5a6..89e6819c1d0314 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -24,16 +24,19 @@ import fontSize from './font-size'; import textAlign from './text-align'; import border from './border'; import position from './position'; +import blockStyleVariation from './block-style-variation'; import layout from './layout'; import childLayout from './layout-child'; import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; +import blockBindingsPanel from './block-bindings'; import './block-renaming'; import './use-bindings-attributes'; createBlockEditFilter( [ + blockBindingsPanel, align, textAlign, anchor, @@ -59,6 +62,7 @@ createBlockListBlockFilter( [ fontSize, border, position, + blockStyleVariation, childLayout, ] ); createBlockSaveFilter( [ diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 88192ddf5f055e..a19c5604701f6d 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -32,6 +32,7 @@ import { useBlockSettings, useStyleOverride } from './utils'; import { unlock } from '../lock-unlock'; const layoutBlockSupportKey = 'layout'; +const { kebabCase } = unlock( componentsPrivateApis ); function hasLayoutBlockSupport( blockName ) { return ( @@ -49,7 +50,6 @@ function hasLayoutBlockSupport( blockName ) { * @return { Array } Array of CSS classname strings. */ export function useLayoutClasses( blockAttributes = {}, blockName = '' ) { - const { kebabCase } = unlock( componentsPrivateApis ); const { layout } = blockAttributes; const { default: defaultBlockLayout } = getBlockSupport( blockName, layoutBlockSupportKey ) || {}; @@ -371,7 +371,6 @@ function BlockWithLayoutStyles( { ? { ...layout, type: 'constrained' } : layout || defaultBlockLayout || {}; - const { kebabCase } = unlock( componentsPrivateApis ); const selectorPrefix = `wp-container-${ kebabCase( name ) }-is-layout-`; // Higher specificity to override defaults from theme.json. const selector = `.${ selectorPrefix }${ id }`; diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 3c9cde478cdd04..a5d4a0cb43bf3d 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -303,7 +303,7 @@ export function PositionPanelPure( { onChange={ ( { selectedItem } ) => { onChangeType( selectedItem.value ); } } - size={ '__unstable-large' } + size="__unstable-large" /> diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index dad62bc0594a75..4cd33350224541 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -332,7 +332,15 @@ function BlockStyleControls( { clientId, name, setAttributes, - settings, + settings: { + ...settings, + typography: { + ...settings.typography, + // The text alignment UI for individual blocks is rendered in + // the block toolbar, so disable it here. + textAlign: false, + }, + }, }; if ( blockEditingMode !== 'default' ) { return null; diff --git a/packages/block-editor/src/hooks/text-align.js b/packages/block-editor/src/hooks/text-align.js index 7735a65ecc5764..af20404d79eccf 100644 --- a/packages/block-editor/src/hooks/text-align.js +++ b/packages/block-editor/src/hooks/text-align.js @@ -15,7 +15,11 @@ import { alignLeft, alignRight, alignCenter } from '@wordpress/icons'; */ import { AlignmentControl, BlockControls } from '../components'; import { useBlockEditingMode } from '../components/block-editing-mode'; -import { cleanEmptyObject, shouldSkipSerialization } from './utils'; +import { + cleanEmptyObject, + shouldSkipSerialization, + useBlockSettings, +} from './utils'; import { TYPOGRAPHY_SUPPORT_KEY } from './typography'; export const TEXT_ALIGN_SUPPORT_KEY = 'typography.textAlign'; @@ -39,6 +43,7 @@ const TEXT_ALIGNMENT_OPTIONS = [ ]; const VALID_TEXT_ALIGNMENTS = [ 'left', 'center', 'right' ]; +const NO_TEXT_ALIGNMENTS = []; /** * Returns the valid text alignments. @@ -50,19 +55,13 @@ const VALID_TEXT_ALIGNMENTS = [ 'left', 'center', 'right' ]; * @return {string[]} Valid text alignments. */ export function getValidTextAlignments( blockTextAlign ) { - let validTextAlignments; if ( Array.isArray( blockTextAlign ) ) { - validTextAlignments = VALID_TEXT_ALIGNMENTS.filter( ( textAlign ) => + return VALID_TEXT_ALIGNMENTS.filter( ( textAlign ) => blockTextAlign.includes( textAlign ) ); - } else if ( blockTextAlign === true ) { - // `true` includes all alignments... - validTextAlignments = [ ...VALID_TEXT_ALIGNMENTS ]; - } else { - validTextAlignments = []; } - return validTextAlignments; + return blockTextAlign === true ? VALID_TEXT_ALIGNMENTS : NO_TEXT_ALIGNMENTS; } function BlockEditTextAlignmentToolbarControlsPure( { @@ -70,11 +69,18 @@ function BlockEditTextAlignmentToolbarControlsPure( { name: blockName, setAttributes, } ) { + const settings = useBlockSettings( blockName ); + const hasTextAlignControl = settings?.typography?.textAlign; + const blockEditingMode = useBlockEditingMode(); + + if ( ! hasTextAlignControl || blockEditingMode !== 'default' ) { + return null; + } + const validTextAlignments = getValidTextAlignments( getBlockSupport( blockName, TEXT_ALIGN_SUPPORT_KEY ) ); - const blockEditingMode = useBlockEditingMode(); - if ( ! validTextAlignments.length || blockEditingMode !== 'default' ) { + if ( ! validTextAlignments.length ) { return null; } diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index b7a4ca0379dd1b..7bd5df05d31eb4 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -4,7 +4,7 @@ import { store as blocksStore } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useRegistry, useSelect } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; /** @@ -29,6 +29,41 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], }; +const DEFAULT_ATTRIBUTE = '__default'; + +/** + * Returns the bindings with the `__default` binding for pattern overrides + * replaced with the full-set of supported attributes. e.g.: + * + * bindings passed in: `{ __default: { source: 'core/pattern-overrides' } }` + * bindings returned: `{ content: { source: 'core/pattern-overrides' } }` + * + * @param {string} blockName The block name (e.g. 'core/paragraph'). + * @param {Object} bindings A block's bindings from the metadata attribute. + * + * @return {Object} The bindings with default replaced for pattern overrides. + */ +function replacePatternOverrideDefaultBindings( blockName, bindings ) { + // The `__default` binding currently only works for pattern overrides. + if ( + bindings?.[ DEFAULT_ATTRIBUTE ]?.source === 'core/pattern-overrides' + ) { + const supportedAttributes = BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; + const bindingsWithDefaults = {}; + for ( const attributeName of supportedAttributes ) { + // If the block has mixed binding sources, retain any non pattern override bindings. + const bindingSource = bindings[ attributeName ] + ? bindings[ attributeName ] + : { source: 'core/pattern-overrides' }; + bindingsWithDefaults[ attributeName ] = bindingSource; + } + + return bindingsWithDefaults; + } + + return bindings; +} + /** * Based on the given block name, * check if it is possible to bind the block. @@ -61,8 +96,18 @@ export const withBlockBindingSupport = createHigherOrderComponent( const sources = useSelect( ( select ) => unlock( select( blocksStore ) ).getAllBlockBindingsSources() ); - const bindings = props.attributes.metadata?.bindings; const { name, clientId, context } = props; + const hasPatternOverridesDefaultBinding = + props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ] + ?.source === 'core/pattern-overrides'; + const bindings = useMemo( + () => + replacePatternOverrideDefaultBindings( + name, + props.attributes.metadata?.bindings + ), + [ props.attributes.metadata?.bindings, name ] + ); const boundAttributes = useSelect( () => { if ( ! bindings ) { return; @@ -128,8 +173,8 @@ export const withBlockBindingSupport = createHigherOrderComponent( continue; } - const source = - sources[ bindings[ attributeName ].source ]; + const binding = bindings[ attributeName ]; + const source = sources[ binding?.source ]; if ( ! source?.setValue && ! source?.setValues ) { continue; } @@ -157,12 +202,13 @@ export const withBlockBindingSupport = createHigherOrderComponent( attributeName, value, ] of Object.entries( attributes ) ) { + const binding = bindings[ attributeName ]; source.setValue( { registry, context, clientId, attributeName, - args: bindings[ attributeName ].args, + args: binding.args, value, } ); } @@ -170,7 +216,13 @@ export const withBlockBindingSupport = createHigherOrderComponent( } } - if ( Object.keys( keptAttributes ).length ) { + // Only apply normal attribute updates to blocks + // that have partial bindings. Currently this is + // only skipped for pattern overrides sources. + if ( + ! hasPatternOverridesDefaultBinding && + Object.keys( keptAttributes ).length + ) { setAttributes( keptAttributes ); } } ); @@ -183,6 +235,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( context, setAttributes, sources, + hasPatternOverridesDefaultBinding, ] ); diff --git a/packages/block-editor/src/hooks/use-typography-props.js b/packages/block-editor/src/hooks/use-typography-props.js index ec76cbf4bf6a06..0f1a2caefde048 100644 --- a/packages/block-editor/src/hooks/use-typography-props.js +++ b/packages/block-editor/src/hooks/use-typography-props.js @@ -16,6 +16,8 @@ import { getFontSizeClass } from '../components/font-sizes'; import { getTypographyFontSizeValue } from '../components/global-styles/typography-utils'; import { unlock } from '../lock-unlock'; +const { kebabCase } = unlock( componentsPrivateApis ); + /* * This utility is intended to assist where the serialization of the typography * block support is being skipped for a block but the typography related CSS @@ -31,7 +33,6 @@ import { unlock } from '../lock-unlock'; * @return {Object} Typography block support derived CSS classes & styles. */ export function getTypographyClassesAndStyles( attributes, settings ) { - const { kebabCase } = unlock( componentsPrivateApis ); let typographyStyles = attributes?.style?.typography || {}; typographyStyles = { ...typographyStyles, diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index ce20cb5bd7a179..3ec701cfc4a14d 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -29,7 +29,10 @@ export function useZoomOut( zoomOut = true ) { return () => { // We need to use __unstableGetEditorMode() here and not `mode`, as mode may not update on unmount - if ( __unstableGetEditorMode() !== originalEditingMode.current ) { + if ( + __unstableGetEditorMode() === 'zoom-out' && + __unstableGetEditorMode() !== originalEditingMode.current + ) { __unstableSetEditorMode( originalEditingMode.current ); } }; @@ -39,7 +42,11 @@ export function useZoomOut( zoomOut = true ) { useEffect( () => { if ( zoomOut && mode !== 'zoom-out' ) { __unstableSetEditorMode( 'zoom-out' ); - } else if ( ! zoomOut && originalEditingMode.current !== mode ) { + } else if ( + ! zoomOut && + __unstableGetEditorMode() === 'zoom-out' && + originalEditingMode.current !== mode + ) { __unstableSetEditorMode( originalEditingMode.current ); } }, [ __unstableSetEditorMode, zoomOut, mode ] ); diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 391287afb6ba09..4338262300c618 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -240,7 +240,11 @@ export function useBlockSettings( name, parentLayout ) { padding, margin, blockGap, - spacingSizes, + defaultSpacingSizesEnabled, + customSpacingSize, + userSpacingSizes, + defaultSpacingSizes, + themeSpacingSizes, units, aspectRatio, minHeight, @@ -293,7 +297,11 @@ export function useBlockSettings( name, parentLayout ) { 'spacing.padding', 'spacing.margin', 'spacing.blockGap', - 'spacing.spacingSizes', + 'spacing.defaultSpacingSizes', + 'spacing.customSpacingSize', + 'spacing.spacingSizes.custom', + 'spacing.spacingSizes.default', + 'spacing.spacingSizes.theme', 'spacing.units', 'dimensions.aspectRatio', 'dimensions.minHeight', @@ -384,8 +392,12 @@ export function useBlockSettings( name, parentLayout ) { }, spacing: { spacingSizes: { - custom: spacingSizes, + custom: userSpacingSizes, + default: defaultSpacingSizes, + theme: themeSpacingSizes, }, + customSpacingSize, + defaultSpacingSizes: defaultSpacingSizesEnabled, padding, margin, blockGap, @@ -428,7 +440,11 @@ export function useBlockSettings( name, parentLayout ) { padding, margin, blockGap, - spacingSizes, + defaultSpacingSizesEnabled, + customSpacingSize, + userSpacingSizes, + defaultSpacingSizes, + themeSpacingSizes, units, aspectRatio, minHeight, @@ -595,6 +611,7 @@ export function createBlockListBlockFilter( features ) { // function reference. setAllWrapperProps={ setAllWrapperProps } name={ props.name } + clientId={ props.clientId } // This component is pure, so only pass needed // props!!! { ...neededProps } diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index f628a9bf3c3f66..72beca11b3f0c9 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -403,12 +403,12 @@ function OrientationControl( { layout, onChange } ) { > diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index e3f956f6dc13d7..4528de117c45b0 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -203,7 +203,7 @@ function GridLayoutMinimumWidthControl( { layout, onChange } ) { { onChange( { ...layout, @@ -251,7 +251,7 @@ function GridLayoutColumnsAndRowsControl( { { /** * If the input is cleared, avoid switching @@ -296,7 +296,7 @@ function GridLayoutColumnsAndRowsControl( { { onChange( { ...layout, @@ -372,12 +372,12 @@ function GridLayoutTypeControl( { layout, onChange } ) { isBlock > diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index a064d7a389ecf8..e6f3fc4cc39d6a 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -40,6 +40,10 @@ import { import { requiresWrapperOnCopy } from './components/writing-flow/utils'; import { PrivateRichText } from './components/rich-text/'; import { PrivateBlockPopover } from './components/block-popover'; +import { PrivateInserterLibrary } from './components/inserter/library'; +import { PrivatePublishDateTimePicker } from './components/publish-date-time-picker'; +import useSpacingSizes from './components/spacing-sizes-control/hooks/use-spacing-sizes'; +import useBlockDisplayTitle from './components/block-title/use-block-display-title'; /** * Private @wordpress/block-editor APIs. @@ -78,6 +82,10 @@ lock( privateApis, { selectBlockPatternsKey, requiresWrapperOnCopy, PrivateRichText, + PrivateInserterLibrary, reusableBlocksSelectKey, PrivateBlockPopover, + PrivatePublishDateTimePicker, + useSpacingSizes, + useBlockDisplayTitle, } ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index d6eaac9c7e8c9b..d80b8202a371a5 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -18,6 +18,7 @@ import { } from '@wordpress/blocks'; import { speak } from '@wordpress/a11y'; import { __, _n, sprintf } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; import { create, insert, remove, toHTMLString } from '@wordpress/rich-text'; import deprecated from '@wordpress/deprecated'; @@ -408,7 +409,7 @@ const createOnMove = ( clientIds, rootClientId ) => ( { select, dispatch } ) => { // If one of the blocks is locked or the parent is locked, we cannot move any block. - const canMoveBlocks = select.canMoveBlocks( clientIds, rootClientId ); + const canMoveBlocks = select.canMoveBlocks( clientIds ); if ( ! canMoveBlocks ) { return; } @@ -430,10 +431,7 @@ export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); export const moveBlocksToPosition = ( clientIds, fromRootClientId = '', toRootClientId = '', index ) => ( { select, dispatch } ) => { - const canMoveBlocks = select.canMoveBlocks( - clientIds, - fromRootClientId - ); + const canMoveBlocks = select.canMoveBlocks( clientIds ); // If one of the blocks is locked or the parent is locked, we cannot move any block. if ( ! canMoveBlocks ) { @@ -442,10 +440,7 @@ export const moveBlocksToPosition = // If moving inside the same root block the move is always possible. if ( fromRootClientId !== toRootClientId ) { - const canRemoveBlocks = select.canRemoveBlocks( - clientIds, - fromRootClientId - ); + const canRemoveBlocks = select.canRemoveBlocks( clientIds ); // If we're moving to another block, it means we're deleting blocks from // the original block, so we need to check if removing is possible. @@ -872,6 +867,30 @@ export const __unstableSplitSelection = typeof selectionB.attributeKey === 'string' ? selectionB.attributeKey : findRichTextAttributeKey( blockBType ); + const blockAttributes = select.getBlockAttributes( + selectionA.clientId + ); + const bindings = blockAttributes?.metadata?.bindings; + + // If the attribute is bound, don't split the selection and insert a new block instead. + if ( bindings?.[ attributeKeyA ] ) { + // Show warning if user tries to insert a block into another block with bindings. + if ( blocks.length ) { + const { createWarningNotice } = + registry.dispatch( noticesStore ); + createWarningNotice( + __( + "Blocks can't be inserted into other blocks with bindings" + ), + { + type: 'snackbar', + } + ); + return; + } + dispatch.insertAfterBlock( selectionA.clientId ); + return; + } // Can't split if the selection is not set. if ( @@ -918,9 +937,7 @@ export const __unstableSplitSelection = ); } - const length = select.getBlockAttributes( selectionA.clientId )[ - attributeKeyA - ].length; + const length = blockAttributes[ attributeKeyA ].length; if ( selectionA.offset === 0 && length ) { dispatch.insertBlocks( @@ -1000,7 +1017,10 @@ export const __unstableSplitSelection = const first = firstBlocks.shift(); head = { ...head, - attributes: headType.merge( head.attributes, first.attributes ), + attributes: { + ...head.attributes, + ...headType.merge( head.attributes, first.attributes ), + }, }; output.push( head ); selection = { @@ -1034,10 +1054,10 @@ export const __unstableSplitSelection = const last = lastBlocks.pop(); output.push( { ...tail, - attributes: tailType.merge( - last.attributes, - tail.attributes - ), + attributes: { + ...tail.attributes, + ...tailType.merge( last.attributes, tail.attributes ), + }, } ); output.push( ...lastBlocks ); selection = { @@ -1506,11 +1526,18 @@ export const insertDefaultBlock = }; /** - * Action that changes the nested settings of a given block. + * @typedef {Object< string, Object >} SettingsByClientId + */ + +/** + * Action that changes the nested settings of the given block(s). * - * @param {string} clientId Client ID of the block whose nested setting are - * being received. - * @param {Object} settings Object with the new settings for the nested block. + * @param {string | SettingsByClientId} clientId Client ID of the block whose + * nested setting are being + * received, or object of settings + * by client ID. + * @param {Object} settings Object with the new settings + * for the nested block. * * @return {Object} Action object */ @@ -1977,7 +2004,7 @@ export function __unstableSetTemporarilyEditingAsBlocks( * per_page: 'page_size', * search: 'q', * }; - * const url = new URL( 'https://api.openverse.engineering/v1/images/' ); + * const url = new URL( 'https://api.openverse.org/v1/images/' ); * Object.entries( finalQuery ).forEach( ( [ key, value ] ) => { * const queryKey = mapFromInserterMediaRequest[ key ] || key; * url.searchParams.set( queryKey, value ); diff --git a/packages/block-editor/src/store/get-block-settings.js b/packages/block-editor/src/store/get-block-settings.js index 1bffebf931e818..9ea8fab15f597a 100644 --- a/packages/block-editor/src/store/get-block-settings.js +++ b/packages/block-editor/src/store/get-block-settings.js @@ -91,51 +91,6 @@ const removeCustomPrefixes = ( path ) => { return prefixedFlags[ path ] || path; }; -/** - * For settings like `color.palette`, which have a value that is an object - * with `default`, `theme`, `custom`, with field values that are arrays of - * items, merge these three arrays into one and return it. The calculation - * is memoized so that identical input values produce identical output. - * @param {Object} value Object to merge - * @return {Array} Array of merged items - */ -export function mergeOrigins( value ) { - let result = mergeCache.get( value ); - if ( ! result ) { - result = [ 'default', 'theme', 'custom' ].flatMap( - ( key ) => value[ key ] ?? [] - ); - mergeCache.set( value, result ); - } - return result; -} -const mergeCache = new WeakMap(); - -/** - * For settings like `color.palette`, which have a value that is an object - * with `default`, `theme`, `custom`, with field values that are arrays of - * items, returns the one with the highest priority among these three arrays. - * @param {Object} value Object to extract from - * @return {Array} Array of items extracted from the three origins - */ -export function overrideOrigins( value ) { - return value.custom ?? value.theme ?? value.default; -} - -/** - * For settings like `color.palette`, which have a value that is an object - * with `default`, `theme`, `custom`, with field values that are arrays of - * items, see if any of the three origins have values. - * - * @param {Object} value Object to check - * @return {boolean} Whether the object has values in any of the three origins - */ -export function hasOriginValue( value ) { - return [ 'default', 'theme', 'custom' ].some( - ( key ) => value?.[ key ]?.length - ); -} - export function getBlockSettings( state, clientId, ...paths ) { const blockName = getBlockName( state, clientId ); const candidates = []; @@ -215,7 +170,7 @@ export function getBlockSettings( state, clientId, ...paths ) { // Return if the setting was found in either the block instance or the store. if ( result !== undefined ) { if ( PATHS_WITH_OVERRIDE[ normalizedPath ] ) { - return overrideOrigins( result ); + return result.custom ?? result.theme ?? result.default; } return result; } diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 28a7b1da98f73f..74aec3c49d1e89 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -6,7 +6,6 @@ import { Platform } from '@wordpress/element'; /** * Internal dependencies */ -import { undoIgnoreBlocks } from './undo-ignore'; import { store as blockEditorStore } from './index'; import { unlock } from '../lock-unlock'; @@ -105,11 +104,7 @@ export const privateRemoveBlocks = } clientIds = castArray( clientIds ); - const rootClientId = select.getBlockRootClientId( clientIds[ 0 ] ); - const canRemoveBlocks = select.canRemoveBlocks( - clientIds, - rootClientId - ); + const canRemoveBlocks = select.canRemoveBlocks( clientIds ); if ( ! canRemoveBlocks ) { return; @@ -292,34 +287,6 @@ export function deleteStyleOverride( id ) { }; } -/** - * A higher-order action that mark every change inside a callback as "non-persistent" - * and ignore pushing to the undo history stack. It's primarily used for synchronized - * derived updates from the block editor without affecting the undo history. - * - * @param {() => void} callback The synchronous callback to derive updates. - */ -export function syncDerivedUpdates( callback ) { - return ( { dispatch, select, registry } ) => { - registry.batch( () => { - // Mark every change in the `callback` as non-persistent. - dispatch( { - type: 'SET_EXPLICIT_PERSISTENT', - isPersistentChange: false, - } ); - callback(); - dispatch( { - type: 'SET_EXPLICIT_PERSISTENT', - isPersistentChange: undefined, - } ); - - // Ignore pushing undo stack for the updated blocks. - const updatedBlocks = select.getBlocks(); - undoIgnoreBlocks.add( updatedBlocks ); - } ); - }; -} - /** * Action that sets the element that had focus when focus leaves the editor canvas. * diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 3ea0fb46273049..7c83887876919f 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1750,24 +1750,37 @@ export const blockListSettings = ( state = {}, action ) => { ); } case 'UPDATE_BLOCK_LIST_SETTINGS': { - const { clientId } = action; - if ( ! action.settings ) { - if ( state.hasOwnProperty( clientId ) ) { - const { [ clientId ]: removedBlock, ...restBlocks } = state; - return restBlocks; + const updates = + typeof action.clientId === 'string' + ? { [ action.clientId ]: action.settings } + : action.clientId; + + // Remove settings that are the same as the current state. + for ( const clientId in updates ) { + if ( ! updates[ clientId ] ) { + if ( ! state[ clientId ] ) { + delete updates[ clientId ]; + } + } else if ( + fastDeepEqual( state[ clientId ], updates[ clientId ] ) + ) { + delete updates[ clientId ]; } + } + if ( Object.keys( updates ).length === 0 ) { return state; } - if ( fastDeepEqual( state[ clientId ], action.settings ) ) { - return state; + const merged = { ...state, ...updates }; + + for ( const clientId in updates ) { + if ( ! updates[ clientId ] ) { + delete merged[ clientId ]; + } } - return { - ...state, - [ clientId ]: action.settings, - }; + return merged; } } return state; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 176c15557c8386..d78d75e4210c8e 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -22,6 +22,7 @@ import { createSelector, createRegistrySelector } from '@wordpress/data'; * Internal dependencies */ import { + withRootClientIdOptionKey, checkAllowListRecursive, checkAllowList, getAllPatternsDependants, @@ -75,6 +76,8 @@ const EMPTY_ARRAY = []; */ const EMPTY_SET = new Set(); +const EMPTY_OBJECT = {}; + /** * Returns a block's name given its client ID, or null if no block exists with * the client ID. @@ -1709,13 +1712,12 @@ export function canInsertBlocks( state, clientIds, rootClientId = null ) { /** * Determines if the given block is allowed to be deleted. * - * @param {Object} state Editor state. - * @param {string} clientId The block client Id. - * @param {?string} rootClientId Optional root client ID of block list. + * @param {Object} state Editor state. + * @param {string} clientId The block client Id. * * @return {boolean} Whether the given block is allowed to be removed. */ -export function canRemoveBlock( state, clientId, rootClientId = null ) { +export function canRemoveBlock( state, clientId ) { const attributes = getBlockAttributes( state, clientId ); if ( attributes === null ) { return true; @@ -1723,6 +1725,8 @@ export function canRemoveBlock( state, clientId, rootClientId = null ) { if ( attributes.lock?.remove !== undefined ) { return ! attributes.lock.remove; } + + const rootClientId = getBlockRootClientId( state, clientId ); if ( getTemplateLock( state, rootClientId ) ) { return false; } @@ -1733,57 +1737,49 @@ export function canRemoveBlock( state, clientId, rootClientId = null ) { /** * Determines if the given blocks are allowed to be removed. * - * @param {Object} state Editor state. - * @param {string} clientIds The block client IDs to be removed. - * @param {?string} rootClientId Optional root client ID of block list. + * @param {Object} state Editor state. + * @param {string} clientIds The block client IDs to be removed. * * @return {boolean} Whether the given blocks are allowed to be removed. */ -export function canRemoveBlocks( state, clientIds, rootClientId = null ) { - return clientIds.every( ( clientId ) => - canRemoveBlock( state, clientId, rootClientId ) - ); +export function canRemoveBlocks( state, clientIds ) { + return clientIds.every( ( clientId ) => canRemoveBlock( state, clientId ) ); } /** * Determines if the given block is allowed to be moved. * - * @param {Object} state Editor state. - * @param {string} clientId The block client Id. - * @param {?string} rootClientId Optional root client ID of block list. + * @param {Object} state Editor state. + * @param {string} clientId The block client Id. * * @return {boolean | undefined} Whether the given block is allowed to be moved. */ -export function canMoveBlock( state, clientId, rootClientId = null ) { +export function canMoveBlock( state, clientId ) { const attributes = getBlockAttributes( state, clientId ); if ( attributes === null ) { return true; } - if ( getBlockEditingMode( state, rootClientId ) !== 'default' ) { - return false; - } if ( attributes.lock?.move !== undefined ) { return ! attributes.lock.move; } + + const rootClientId = getBlockRootClientId( state, clientId ); if ( getTemplateLock( state, rootClientId ) === 'all' ) { return false; } - return true; + return getBlockEditingMode( state, rootClientId ) !== 'disabled'; } /** * Determines if the given blocks are allowed to be moved. * - * @param {Object} state Editor state. - * @param {string} clientIds The block client IDs to be moved. - * @param {?string} rootClientId Optional root client ID of block list. + * @param {Object} state Editor state. + * @param {string} clientIds The block client IDs to be moved. * * @return {boolean} Whether the given blocks are allowed to be moved. */ -export function canMoveBlocks( state, clientIds, rootClientId = null ) { - return clientIds.every( ( clientId ) => - canMoveBlock( state, clientId, rootClientId ) - ); +export function canMoveBlocks( state, clientIds ) { + return clientIds.every( ( clientId ) => canMoveBlock( state, clientId ) ); } /** @@ -1998,7 +1994,7 @@ const buildBlockTypeItem = */ export const getInserterItems = createRegistrySelector( ( select ) => createSelector( - ( state, rootClientId = null ) => { + ( state, rootClientId = null, options = EMPTY_OBJECT ) => { const buildReusableBlockInserterItem = ( reusableBlock ) => { const icon = ! reusableBlock.wp_pattern_sync_status ? { @@ -2040,16 +2036,73 @@ export const getInserterItems = createRegistrySelector( ( select ) => buildScope: 'inserter', } ); - const blockTypeInserterItems = getBlockTypes() + let blockTypeInserterItems = getBlockTypes() .filter( ( blockType ) => - canIncludeBlockTypeInInserter( - state, - blockType, - rootClientId - ) + hasBlockSupport( blockType, 'inserter', true ) ) .map( buildBlockTypeInserterItem ); + if ( options[ withRootClientIdOptionKey ] ) { + blockTypeInserterItems = blockTypeInserterItems.reduce( + ( accumulator, item ) => { + item.rootClientId = rootClientId ?? ''; + + while ( + ! canInsertBlockTypeUnmemoized( + state, + item.name, + item.rootClientId + ) + ) { + if ( ! item.rootClientId ) { + let sectionRootClientId; + try { + sectionRootClientId = unlock( + getSettings( state ) + ).sectionRootClientId; + } catch ( e ) {} + if ( + sectionRootClientId && + canInsertBlockTypeUnmemoized( + state, + item.name, + sectionRootClientId + ) + ) { + item.rootClientId = sectionRootClientId; + } else { + delete item.rootClientId; + } + break; + } else { + const parentClientId = getBlockRootClientId( + state, + item.rootClientId + ); + item.rootClientId = parentClientId; + } + } + + // We could also add non insertable items and gray them out. + if ( item.hasOwnProperty( 'rootClientId' ) ) { + accumulator.push( item ); + } + + return accumulator; + }, + [] + ); + } else { + blockTypeInserterItems = blockTypeInserterItems.filter( + ( blockType ) => + canIncludeBlockTypeInInserter( + state, + blockType, + rootClientId + ) + ); + } + const items = blockTypeInserterItems.reduce( ( accumulator, item ) => { const { variations = [] } = item; @@ -2984,10 +3037,7 @@ export const isGroupable = createRegistrySelector( rootClientId ); const _isGroupable = groupingBlockAvailable && _clientIds.length; - return ( - _isGroupable && - canRemoveBlocks( state, _clientIds, rootClientId ) - ); + return _isGroupable && canRemoveBlocks( state, _clientIds ); } ); diff --git a/packages/block-editor/src/store/undo-ignore.js b/packages/block-editor/src/store/undo-ignore.js deleted file mode 100644 index f0a64428ea7c26..00000000000000 --- a/packages/block-editor/src/store/undo-ignore.js +++ /dev/null @@ -1,4 +0,0 @@ -// Keep track of the blocks that should not be pushing an additional -// undo stack when editing the entity. -// See the implementation of `syncDerivedUpdates` and `useBlockSync`. -export const undoIgnoreBlocks = new WeakSet(); diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js index f236c4a7e56eb8..c94453e99c60a4 100644 --- a/packages/block-editor/src/store/utils.js +++ b/packages/block-editor/src/store/utils.js @@ -5,6 +5,8 @@ import { selectBlockPatternsKey } from './private-keys'; import { unlock } from '../lock-unlock'; import { STORE_NAME } from './constants'; +export const withRootClientIdOptionKey = Symbol( 'withRootClientId' ); + export const checkAllowList = ( list, item, defaultResult = null ) => { if ( typeof list === 'boolean' ) { return list; diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 5080aa05718bb3..cf4683b02c707d 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -1,6 +1,5 @@ @import "./autocompleters/style.scss"; @import "./components/block-alignment-control/style.scss"; -@import "./components/block-bindings-toolbar-indicator/style.scss"; @import "./components/block-canvas/style.scss"; @import "./components/block-icon/style.scss"; @import "./components/block-inspector/style.scss"; @@ -46,6 +45,7 @@ @import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss"; +@import "./hooks/block-bindings.scss"; @import "./hooks/block-hooks.scss"; @import "./hooks/border.scss"; @import "./hooks/color.scss"; diff --git a/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap b/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap index 35b2adfae17249..9da07667d0a3e0 100644 --- a/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap +++ b/packages/block-editor/src/utils/test/__snapshots__/transform-styles.js.snap @@ -24,7 +24,7 @@ exports[`transformStyles URL rewrite should rewrite relative paths 1`] = ` ] `; -exports[`transformStyles error handling should handle multiple instances of \`:where(body)\` 1`] = ` +exports[`transformStyles error handling should handle multiple instances of \`:root :where(body)\` 1`] = ` [ ".my-namespace { color: pink; } .my-namespace { color: orange; }", ] diff --git a/packages/block-editor/src/utils/test/transform-styles.js b/packages/block-editor/src/utils/test/transform-styles.js index 105c92348113a9..8245ce62831078 100644 --- a/packages/block-editor/src/utils/test/transform-styles.js +++ b/packages/block-editor/src/utils/test/transform-styles.js @@ -52,8 +52,8 @@ describe( 'transformStyles', () => { ); } ); - it( 'should handle multiple instances of `:where(body)`', () => { - const input = `:where(body) { color: pink; } :where(body) { color: orange; }`; + it( 'should handle multiple instances of `:root :where(body)`', () => { + const input = `:root :where(body) { color: pink; } :root :where(body) { color: orange; }`; const output = transformStyles( [ { diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index 95eb43e874fd28..9d57de3fa3833c 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -18,7 +18,9 @@ function transformStyle( if ( ! wrapperSelector && ! baseURL ) { return css; } - const postcssFriendlyCSS = css.replace( /:where\(body\)/g, 'body' ); + const postcssFriendlyCSS = css + .replace( /:root :where\(body\)/g, 'body' ) + .replace( /:where\(body\)/g, 'body' ); try { return postcss( [ diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index c938638b9d06f1..b8a1f41dabd892 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +## 9.1.0 (2024-06-15) + +## 9.0.0 (2024-05-31) + +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). +- Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). + ## 8.35.0 (2024-05-16) ### Internal @@ -84,7 +93,7 @@ ### Breaking Changes -- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) +- Updated dependencies to require React 18 ([#45235](https://github.com/WordPress/gutenberg/pull/45235)) ## 7.19.0 (2022-11-16) diff --git a/packages/block-library/README.md b/packages/block-library/README.md index 25c5f9b52c71a4..28cf7471ed1f14 100644 --- a/packages/block-library/README.md +++ b/packages/block-library/README.md @@ -66,7 +66,7 @@ To find out more about contributing to this package or Gutenberg as a whole, ple ⚠️ Adding new blocks to this package **requires** additional steps! -1. Do not forget to register a new core block in the [`index.js`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/index.js) file of this package. For example, if you were to add the new core block called `core/blinking-paragraph`, you would have to add something like: +1. Do not forget to register a new core block in the [`index.js`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/index.js) file of this package. For example, if you were to add the new core block called `core/blinking-paragraph`, you would have to add something like: ```js // packages/block-library/src/index.js diff --git a/packages/block-library/package.json b/packages/block-library/package.json index c5aa6fbb99992e..909fc9a53ee679 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "8.35.0", + "version": "9.1.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -19,7 +19,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "engines": { - "node": ">=12" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/block-library/src/audio/theme.scss b/packages/block-library/src/audio/theme.scss index eda394fd6a3d54..2744d36e74ca62 100644 --- a/packages/block-library/src/audio/theme.scss +++ b/packages/block-library/src/audio/theme.scss @@ -2,6 +2,6 @@ @include caption-style-theme(); } -:where(.wp-block-audio) { +.wp-block-audio { margin: 0 0 1em 0; } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index a4f054db46665a..8d4147cb318555 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -40,7 +40,8 @@ import { name as patternBlockName } from './index'; import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); -const { isOverridableBlock } = unlock( patternsPrivateApis ); +const { isOverridableBlock, hasOverridableBlocks } = + unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; @@ -73,15 +74,6 @@ const useInferredLayout = ( blocks, parentLayout ) => { }, [ blocks, parentLayout ] ); }; -function hasOverridableBlocks( blocks ) { - return blocks.some( ( block ) => { - if ( isOverridableBlock( block ) ) { - return true; - } - return hasOverridableBlocks( block.innerBlocks ); - } ); -} - function setBlockEditMode( setEditMode, blocks, mode ) { blocks.forEach( ( block ) => { const editMode = diff --git a/packages/block-library/src/block/edit.native.js b/packages/block-library/src/block/edit.native.js index ae8c8315aa2e88..6578c12eefc4e0 100644 --- a/packages/block-library/src/block/edit.native.js +++ b/packages/block-library/src/block/edit.native.js @@ -232,7 +232,7 @@ export default function ReusableBlockEdit( { diff --git a/packages/block-library/src/block/editor.scss b/packages/block-library/src/block/editor.scss deleted file mode 100644 index 318101f7f52dac..00000000000000 --- a/packages/block-library/src/block/editor.scss +++ /dev/null @@ -1,35 +0,0 @@ -.edit-post-visual-editor .block-library-block__reusable-block-container { - // Unset the padding that root containers get when they're actually root containers. - .is-root-container { - padding-left: 0; - padding-right: 0; - } - - // Allow vertical paddings to collapse to better fit the flow. - .block-editor-writing-flow { - display: block; - } - - .components-disabled .block-list-appender { - display: none; - } -} - -.edit-post-visual-editor .block-editor-block-list__block:not(.remove-outline).is-reusable { - - &.is-highlighted::after, - &.is-selected::after { - outline-color: var(--wp-block-synced-color); - } - - &.block-editor-block-list__block:not([contenteditable]):focus { - &::after { - outline-color: var(--wp-block-synced-color); - - // Show a light color for dark themes. - .is-dark-theme & { - outline-color: $dark-theme-focus; - } - } - } -} diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 9403205c186596..8beef975fad6f3 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -78,7 +78,7 @@ function render_block_core_block( $attributes ) { * filter so that it is available when a pattern's inner blocks are * rendering via do_blocks given it only receives the inner content. */ - $has_pattern_overrides = isset( $attributes['content'] ); + $has_pattern_overrides = isset( $attributes['content'] ) && null !== get_block_bindings_source( 'core/pattern-overrides' ); if ( $has_pattern_overrides ) { $filter_block_context = static function ( $context ) use ( $attributes ) { $context['pattern/overrides'] = $attributes['content']; diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 155fe797e3147d..e5bd5e6b5f0643 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -156,6 +156,7 @@ function ButtonEdit( props ) { onReplace, mergeBlocks, clientId, + context, } = props; const { tagName, @@ -246,11 +247,14 @@ function ButtonEdit( props ) { return { lockUrlControls: !! metadata?.bindings?.url && - ( ! blockBindingsSource || - blockBindingsSource?.lockAttributesEditing() ), + ! blockBindingsSource?.canUserEditValue( { + select, + context, + args: metadata?.bindings?.url?.args, + } ), }; }, - [ isSelected ] + [ isSelected, metadata?.bindings?.url ] ); return ( @@ -339,7 +343,7 @@ function ButtonEdit( props ) { } } anchor={ popoverAnchor } focusOnMount={ isEditingURL ? 'firstElement' : false } - __unstableSlotName={ '__unstable-block-tools-after' } + __unstableSlotName="__unstable-block-tools-after" shift > ` or ` diff --git a/packages/block-library/src/template-part/theme.scss b/packages/block-library/src/template-part/theme.scss index 2f1d3a0a513cb7..179873b9448b82 100644 --- a/packages/block-library/src/template-part/theme.scss +++ b/packages/block-library/src/template-part/theme.scss @@ -1,9 +1,7 @@ // Same as the group block styles. -:where(.wp-block-template-part) { - &.has-background { - // Matches paragraph Block padding - padding: $block-bg-padding--v $block-bg-padding--h; - margin-top: 0; - margin-bottom: 0; - } +:root :where(.wp-block-template-part.has-background) { + // Matches paragraph Block padding + padding: $block-bg-padding--v $block-bg-padding--h; + margin-top: 0; + margin-bottom: 0; } diff --git a/packages/block-library/src/utils/caption.js b/packages/block-library/src/utils/caption.js index 2c653a9cc7fb8c..82e951d4813eb1 100644 --- a/packages/block-library/src/utils/caption.js +++ b/packages/block-library/src/utils/caption.js @@ -32,6 +32,7 @@ export function Caption( { placeholder = __( 'Add caption' ), label = __( 'Caption text' ), showToolbarButton = true, + excludeElementClassName, className, readOnly, tagName = 'figcaption', @@ -70,6 +71,7 @@ export function Caption( { }, [ isCaptionEmpty ] ); + return ( <> { showToolbarButton && ( @@ -96,7 +98,9 @@ export function Caption( { tagName={ tagName } className={ clsx( className, - __experimentalGetElementClassName( 'caption' ) + excludeElementClassName + ? '' + : __experimentalGetElementClassName( 'caption' ) ) } ref={ ref } aria-label={ label } diff --git a/packages/block-library/src/utils/hooks.js b/packages/block-library/src/utils/hooks.js index 85196fe67ed7ac..418b0dbebb0c02 100644 --- a/packages/block-library/src/utils/hooks.js +++ b/packages/block-library/src/utils/hooks.js @@ -33,6 +33,7 @@ export function useCanEditEntity( kind, name, recordId ) { */ export function useUploadMediaFromBlobURL( args = {} ) { const latestArgs = useRef( args ); + const hasUploadStarted = useRef( false ); const { getSettings } = useSelect( blockEditorStore ); useLayoutEffect( () => { @@ -40,6 +41,12 @@ export function useUploadMediaFromBlobURL( args = {} ) { } ); useEffect( () => { + // Uploading is a special effect that can't be canceled via the cleanup method. + // The extra check avoids duplicate uploads in development mode (React.StrictMode). + if ( hasUploadStarted.current ) { + return; + } + if ( ! latestArgs.current.url || ! isBlobURL( latestArgs.current.url ) @@ -55,6 +62,8 @@ export function useUploadMediaFromBlobURL( args = {} ) { const { url, allowedTypes, onChange, onError } = latestArgs.current; const { mediaUpload } = getSettings(); + hasUploadStarted.current = true; + mediaUpload( { filesList: [ file ], allowedTypes, @@ -65,10 +74,12 @@ export function useUploadMediaFromBlobURL( args = {} ) { revokeBlobURL( url ); onChange( media ); + hasUploadStarted.current = false; }, onError: ( message ) => { revokeBlobURL( url ); onError( message ); + hasUploadStarted.current = false; }, } ); }, [ getSettings ] ); diff --git a/packages/block-library/src/verse/block.json b/packages/block-library/src/verse/block.json index 1d6b817c003023..8e52c8bcfa30bb 100644 --- a/packages/block-library/src/verse/block.json +++ b/packages/block-library/src/verse/block.json @@ -21,6 +21,13 @@ }, "supports": { "anchor": true, + "background": { + "backgroundImage": true, + "backgroundSize": true, + "__experimentalDefaultControls": { + "backgroundImage": true + } + }, "color": { "gradients": true, "link": true, @@ -29,6 +36,12 @@ "text": true } }, + "dimensions": { + "minHeight": true, + "__experimentalDefaultControls": { + "minHeight": false + } + }, "typography": { "fontSize": true, "__experimentalFontFamily": true, diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js index d781fb31b5f3ac..5ebf431ade3fc0 100644 --- a/packages/block-library/src/video/edit-common-settings.js +++ b/packages/block-library/src/video/edit-common-settings.js @@ -74,9 +74,13 @@ const VideoSettings = ( { setAttributes, attributes } ) => { /> =12" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 7b4ff81fd925fc..a0d79d05b7a9a0 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 5.1.0 (2024-06-15) + +## 5.0.0 (2024-05-31) + +### Breaking Changes + +- Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). + ## 4.58.0 (2024-05-16) ## 4.57.0 (2024-05-02) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 7bb23f4bf291ff..c9cb21ee361483 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.58.0", + "version": "5.1.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,7 +21,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "engines": { - "node": ">=12" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "main": "parser.js", "sideEffects": false, diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 85f09d262242c9..eb1a5db27cc3cf 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +## 13.1.0 (2024-06-15) + +## 13.0.0 (2024-05-31) + +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). +- Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). + ## 12.35.0 (2024-05-16) ## 12.34.0 (2024-05-02) @@ -92,7 +101,8 @@ ## 11.17.0 (2022-09-21) -- The block attribute sources `children` and `node` have been deprecated. Please use the `html` source instead. See https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/ and the core blocks for examples. +- The block attribute sources `children` and `node` have been deprecated. Please use the `html` source instead. See https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/ and the core blocks for examples. + ## 11.16.0 (2022-09-13) ## 11.15.0 (2022-08-24) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index e783b5da04318c..54a434eb0b5406 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "12.35.0", + "version": "13.1.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -19,7 +19,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "engines": { - "node": ">=12" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "main": "build/index.js", "module": "build-module/index.js", @@ -32,7 +33,6 @@ "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", - "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", diff --git a/packages/blocks/src/api/parser/convert-legacy-block.js b/packages/blocks/src/api/parser/convert-legacy-block.js index c828a3f5db6c49..055679302efd64 100644 --- a/packages/blocks/src/api/parser/convert-legacy-block.js +++ b/packages/blocks/src/api/parser/convert-legacy-block.js @@ -79,7 +79,7 @@ export function convertLegacyBlockNameAndAttributes( name, attributes ) { // The following code is only relevant for the Gutenberg plugin. // It's a stand-alone if statement for dead-code elimination. - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { // Convert pattern overrides added during experimental phase. // Only four blocks were supported initially. // These checks can be removed in WordPress 6.6. @@ -88,25 +88,41 @@ export function convertLegacyBlockNameAndAttributes( name, attributes ) { ( name === 'core/paragraph' || name === 'core/heading' || name === 'core/image' || - name === 'core/button' ) + name === 'core/button' ) && + newAttributes.metadata.bindings.__default?.source !== + 'core/pattern-overrides' ) { const bindings = [ 'content', 'url', 'title', + 'id', 'alt', 'text', 'linkTarget', ]; + // Delete any existing individual bindings and add a default binding. + // It was only possible to add all the default attributes through the UI, + // So as soon as we find an attribute, we can assume all default attributes are overridable. + let hasPatternOverrides = false; bindings.forEach( ( binding ) => { if ( - newAttributes.metadata.bindings[ binding ]?.source?.name === - 'pattern_attributes' + newAttributes.metadata.bindings[ binding ]?.source === + 'core/pattern-overrides' ) { - newAttributes.metadata.bindings[ binding ].source = - 'core/pattern-overrides'; + hasPatternOverrides = true; + newAttributes.metadata = { + ...newAttributes.metadata, + bindings: { ...newAttributes.metadata.bindings }, + }; + delete newAttributes.metadata.bindings[ binding ]; } } ); + if ( hasPatternOverrides ) { + newAttributes.metadata.bindings.__default = { + source: 'core/pattern-overrides', + }; + } } } return [ name, newAttributes ]; diff --git a/packages/blocks/src/api/parser/fix-custom-classname.js b/packages/blocks/src/api/parser/fix-custom-classname.js index d72065cd59222b..78f04ea4a00fa5 100644 --- a/packages/blocks/src/api/parser/fix-custom-classname.js +++ b/packages/blocks/src/api/parser/fix-custom-classname.js @@ -42,27 +42,30 @@ export function getHTMLRootElementClasses( innerHTML ) { * @return {Object} Filtered block attributes. */ export function fixCustomClassname( blockAttributes, blockType, innerHTML ) { - if ( hasBlockSupport( blockType, 'customClassName', true ) ) { - // To determine difference, serialize block given the known set of - // attributes, with the exception of `className`. This will determine - // the default set of classes. From there, any difference in innerHTML - // can be considered as custom classes. - const { className: omittedClassName, ...attributesSansClassName } = - blockAttributes; - const serialized = getSaveContent( blockType, attributesSansClassName ); - const defaultClasses = getHTMLRootElementClasses( serialized ); - const actualClasses = getHTMLRootElementClasses( innerHTML ); + if ( ! hasBlockSupport( blockType, 'customClassName', true ) ) { + return blockAttributes; + } + + const modifiedBlockAttributes = { ...blockAttributes }; + // To determine difference, serialize block given the known set of + // attributes, with the exception of `className`. This will determine + // the default set of classes. From there, any difference in innerHTML + // can be considered as custom classes. + const { className: omittedClassName, ...attributesSansClassName } = + modifiedBlockAttributes; + const serialized = getSaveContent( blockType, attributesSansClassName ); + const defaultClasses = getHTMLRootElementClasses( serialized ); + const actualClasses = getHTMLRootElementClasses( innerHTML ); - const customClasses = actualClasses.filter( - ( className ) => ! defaultClasses.includes( className ) - ); + const customClasses = actualClasses.filter( + ( className ) => ! defaultClasses.includes( className ) + ); - if ( customClasses.length ) { - blockAttributes.className = customClasses.join( ' ' ); - } else if ( serialized ) { - delete blockAttributes.className; - } + if ( customClasses.length ) { + modifiedBlockAttributes.className = customClasses.join( ' ' ); + } else if ( serialized ) { + delete modifiedBlockAttributes.className; } - return blockAttributes; + return modifiedBlockAttributes; } diff --git a/packages/blocks/src/api/parser/get-block-attributes.js b/packages/blocks/src/api/parser/get-block-attributes.js index 24faae73704636..36ea86399329e1 100644 --- a/packages/blocks/src/api/parser/get-block-attributes.js +++ b/packages/blocks/src/api/parser/get-block-attributes.js @@ -7,7 +7,6 @@ import memoize from 'memize'; /** * WordPress dependencies */ -import { pipe } from '@wordpress/compose'; import { applyFilters } from '@wordpress/hooks'; import { RichTextData } from '@wordpress/rich-text'; @@ -37,24 +36,8 @@ import { normalizeBlockType, getDefault } from '../utils'; * * @return {Function} Enhanced hpq matcher. */ -export const toBooleanAttributeMatcher = ( matcher ) => - pipe( [ - matcher, - // Expected values from `attr( 'disabled' )`: - // - // - // - Value: `undefined` - // - Transformed: `false` - // - // - // - Value: `''` - // - Transformed: `true` - // - // - // - Value: `'disabled'` - // - Transformed: `true` - ( value ) => value !== undefined, - ] ); +export const toBooleanAttributeMatcher = ( matcher ) => ( value ) => + matcher( value ) !== undefined; /** * Returns true if value is of the given JSON schema type, or false otherwise. @@ -214,13 +197,13 @@ export function isValidByEnum( value, enumSet ) { */ export const matcherFromSource = memoize( ( sourceConfig ) => { switch ( sourceConfig.source ) { - case 'attribute': + case 'attribute': { let matcher = attr( sourceConfig.selector, sourceConfig.attribute ); if ( sourceConfig.type === 'boolean' ) { matcher = toBooleanAttributeMatcher( matcher ); } - return matcher; + } case 'html': return html( sourceConfig.selector, sourceConfig.multiline ); case 'text': @@ -244,12 +227,10 @@ export const matcherFromSource = memoize( ( sourceConfig ) => { ) ); return query( sourceConfig.selector, subMatchers ); - case 'tag': - return pipe( [ - prop( sourceConfig.selector, 'nodeName' ), - ( nodeName ) => - nodeName ? nodeName.toLowerCase() : undefined, - ] ); + case 'tag': { + const matcher = prop( sourceConfig.selector, 'nodeName' ); + return ( domNode ) => matcher( domNode )?.toLowerCase(); + } default: // eslint-disable-next-line no-console console.error( `Unknown source type "${ sourceConfig.source }"` ); diff --git a/packages/blocks/src/api/parser/index.js b/packages/blocks/src/api/parser/index.js index f8ff0c68964dc3..14a88f602987ab 100644 --- a/packages/blocks/src/api/parser/index.js +++ b/packages/blocks/src/api/parser/index.js @@ -178,7 +178,7 @@ function applyBlockValidation( unvalidatedBlock, blockType ) { ); // Attempt to validate the block once again after the built-in fixes. const [ isFixedValid, validationIssues ] = validateBlock( - unvalidatedBlock, + fixedBlock, blockType ); diff --git a/packages/blocks/src/api/parser/test/index.js b/packages/blocks/src/api/parser/test/index.js index 85e51e9d0af025..42923a7d3eeb35 100644 --- a/packages/blocks/src/api/parser/test/index.js +++ b/packages/blocks/src/api/parser/test/index.js @@ -50,6 +50,38 @@ describe( 'block parser', () => { } ); describe( 'parseRawBlock', () => { + it( 'should apply className block validation fixes', () => { + registerBlockType( 'core/test-block', { + ...defaultBlockSettings, + attributes: { + fruit: { + type: 'string', + source: 'text', + selector: 'div', + }, + }, + save: ( { attributes } ) => ( + // eslint-disable-next-line react/no-unknown-property +
+ { attributes.fruit } +
+ ), + } ); + + const block = parseRawBlock( { + blockName: 'core/test-block', + innerHTML: + '
Bananas
', + attrs: { fruit: 'Bananas' }, + } ); + + expect( block.name ).toEqual( 'core/test-block' ); + expect( block.attributes ).toEqual( { + fruit: 'Bananas', + className: 'custom-class another-custom-class', + } ); + } ); + it( 'should create the requested block if it exists', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); diff --git a/packages/blocks/src/api/raw-handling/image-corrector.native.js b/packages/blocks/src/api/raw-handling/image-corrector.native.js index c6a9288ede2d3b..550c2e0e6e1537 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.native.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.native.js @@ -10,7 +10,10 @@ export default function imageCorrector( node ) { return; } - if ( node.src.indexOf( 'file:' ) === 0 ) { + // For local files makes sure the path doesn't end with an invalid extension. + // This scenario often happens with content from MS Word and similar text apps. + // We still need to support local files pasted from the users Media library. + if ( node.src.startsWith( 'file:' ) && node.src.slice( -1 ) === '/' ) { node.setAttribute( 'src', '' ); } diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js index a47d9aacab37ae..dd6650338d9d1a 100644 --- a/packages/blocks/src/store/private-actions.js +++ b/packages/blocks/src/store/private-actions.js @@ -55,6 +55,6 @@ export function registerBlockBindingsSource( source ) { setValue: source.setValue, setValues: source.setValues, getPlaceholder: source.getPlaceholder, - lockAttributesEditing: source.lockAttributesEditing, + canUserEditValue: source.canUserEditValue, }; } diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 1d0d8cb2e968fc..c00810c534d55d 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -381,10 +381,7 @@ export function blockBindingsSources( state = {}, action ) { setValue: action.setValue, setValues: action.setValues, getPlaceholder: action.getPlaceholder, - lockAttributesEditing: () => - action.lockAttributesEditing - ? action.lockAttributesEditing() - : true, + canUserEditValue: action.canUserEditValue || ( () => false ), }, }; } diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index a69879809ab1a6..e97048e92b0c07 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -6,13 +6,13 @@ import removeAccents from 'remove-accents'; /** * WordPress dependencies */ -import { pipe } from '@wordpress/compose'; import { createSelector } from '@wordpress/data'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies */ -import { getValueFromObjectPath } from './utils'; +import { getValueFromObjectPath, matchesAttributes } from './utils'; /** @typedef {import('../api/registration').WPBlockVariation} WPBlockVariation */ /** @typedef {import('../api/registration').WPBlockVariationScope} WPBlockVariationScope */ @@ -238,26 +238,61 @@ export const getBlockVariations = createSelector( export function getActiveBlockVariation( state, blockName, attributes, scope ) { const variations = getBlockVariations( state, blockName, scope ); - const match = variations?.find( ( variation ) => { + if ( ! variations ) { + return variations; + } + + const blockType = getBlockType( state, blockName ); + const attributeKeys = Object.keys( blockType?.attributes || {} ); + let match; + let maxMatchedAttributes = 0; + + for ( const variation of variations ) { if ( Array.isArray( variation.isActive ) ) { - const blockType = getBlockType( state, blockName ); - const attributeKeys = Object.keys( blockType?.attributes || {} ); const definedAttributes = variation.isActive.filter( - ( attribute ) => attributeKeys.includes( attribute ) + ( attribute ) => { + // We support nested attribute paths, e.g. `layout.type`. + // In this case, we need to check if the part before the + // first dot is a known attribute. + const topLevelAttribute = attribute.split( '.' )[ 0 ]; + return attributeKeys.includes( topLevelAttribute ); + } ); - if ( definedAttributes.length === 0 ) { - return false; + const definedAttributesLength = definedAttributes.length; + if ( definedAttributesLength === 0 ) { + continue; } - return definedAttributes.every( - ( attribute ) => - attributes[ attribute ] === - variation.attributes[ attribute ] - ); + const isMatch = definedAttributes.every( ( attribute ) => { + const variationAttributeValue = getValueFromObjectPath( + variation.attributes, + attribute + ); + if ( variationAttributeValue === undefined ) { + return false; + } + let blockAttributeValue = getValueFromObjectPath( + attributes, + attribute + ); + if ( blockAttributeValue instanceof RichTextData ) { + blockAttributeValue = blockAttributeValue.toHTMLString(); + } + return matchesAttributes( + blockAttributeValue, + variationAttributeValue + ); + } ); + if ( isMatch && definedAttributesLength > maxMatchedAttributes ) { + match = variation; + maxMatchedAttributes = definedAttributesLength; + } + } else if ( variation.isActive?.( attributes, variation.attributes ) ) { + // If isActive is a function, we cannot know how many attributes it matches. + // This means that we cannot compare the specificity of our matches, + // and simply return the best match we have found. + return match || variation; } - - return variation.isActive?.( attributes, variation.attributes ); - } ); - + } return match; } @@ -645,6 +680,18 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) { return !! getBlockSupport( state, nameOrType, feature, defaultSupports ); } +/** + * Normalizes a search term string: removes accents, converts to lowercase, removes extra whitespace. + * + * @param {string|null|undefined} term Search term to normalize. + * @return {string} Normalized search term. + */ +function getNormalizedSearchTerm( term ) { + return removeAccents( term ?? '' ) + .toLowerCase() + .trim(); +} + /** * Returns true if the block type by the given name or object value matches a * search term, or false otherwise. @@ -684,30 +731,12 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) { * * @return {Object[]} Whether block type matches search term. */ -export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { +export function isMatchingSearchTerm( state, nameOrType, searchTerm = '' ) { const blockType = getNormalizedBlockType( state, nameOrType ); - - const getNormalizedSearchTerm = pipe( [ - // Disregard diacritics. - // Input: "média" - ( term ) => removeAccents( term ?? '' ), - - // Lowercase. - // Input: "MEDIA" - ( term ) => term.toLowerCase(), - - // Strip leading and trailing whitespace. - // Input: " media " - ( term ) => term.trim(), - ] ); - const normalizedSearchTerm = getNormalizedSearchTerm( searchTerm ); - const isSearchMatch = pipe( [ - getNormalizedSearchTerm, - ( normalizedCandidate ) => - normalizedCandidate.includes( normalizedSearchTerm ), - ] ); + const isSearchMatch = ( candidate ) => + getNormalizedSearchTerm( candidate ).includes( normalizedSearchTerm ); return ( isSearchMatch( blockType.title ) || diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 1fda11d72311a3..1f95809c9674b0 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -17,6 +17,11 @@ import { getActiveBlockVariation, } from '../selectors'; +/** + * WordPress dependencies + */ +import { RichTextData } from '@wordpress/rich-text'; + const keyBlocksByName = ( blocks ) => blocks.reduce( ( result, block ) => ( { ...result, [ block.name ]: block } ), @@ -291,6 +296,7 @@ describe( 'selectors', () => { testAttribute: {}, firstTestAttribute: {}, secondTestAttribute: {}, + thirdTestAttribute: {}, }, }; const FIRST_VARIATION_TEST_ATTRIBUTE_VALUE = 1; @@ -410,6 +416,161 @@ describe( 'selectors', () => { expect( result ).toEqual( variation ); } ); } ); + it( 'should support nested attribute paths in the isActive array', () => { + const variations = [ + { + name: 'variation-1', + attributes: { + firstTestAttribute: { + nestedProperty: 1, + otherNestedProperty: 5555, + }, + }, + isActive: [ 'firstTestAttribute.nestedProperty' ], + }, + { + name: 'variation-2', + attributes: { + firstTestAttribute: { + nestedProperty: 2, + otherNestedProperty: 5555, + }, + }, + isActive: [ 'firstTestAttribute.nestedProperty' ], + }, + ]; + const state = + createBlockVariationsStateWithTestBlockType( variations ); + + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: { + nestedProperty: 1, + }, + } ) + ).toEqual( variations[ 0 ] ); + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: { + nestedProperty: 2, + }, + } ) + ).toEqual( variations[ 1 ] ); + } ); + it( 'should support RichText attributes in the isActive array', () => { + const variations = [ + { + name: 'variation-1', + attributes: { + firstTestAttribute: + 'This is a RichText attribute.', + }, + isActive: [ 'firstTestAttribute' ], + }, + { + name: 'variation-2', + attributes: { + firstTestAttribute: + 'This is a RichText attribute.', + }, + isActive: [ 'firstTestAttribute' ], + }, + ]; + const state = + createBlockVariationsStateWithTestBlockType( variations ); + + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: RichTextData.fromHTMLString( + 'This is a RichText attribute.' + ), + } ) + ).toEqual( variations[ 0 ] ); + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: RichTextData.fromHTMLString( + 'This is a RichText attribute.' + ), + } ) + ).toEqual( variations[ 1 ] ); + } ); + it( 'should compare object attributes in the isActive array based on given properties', () => { + const variations = [ + { + name: 'variation-1', + attributes: { + firstTestAttribute: { + nestedProperty: 1, + secondNestedProperty: 10, + }, + secondTestAttribute: { + nestedProperty: { + firstDeeplyNestedProperty: 'a1', + secondDeeplyNestedProperty: 'a2', + }, + }, + }, + isActive: [ + 'firstTestAttribute', + 'secondTestAttribute.nestedProperty', + ], + }, + { + name: 'variation-2', + attributes: { + firstTestAttribute: { + nestedProperty: 2, + secondNestedProperty: 20, + }, + secondTestAttribute: { + nestedProperty: { + firstDeeplyNestedProperty: 'b1', + secondDeeplyNestedProperty: 'b2', + }, + }, + }, + isActive: [ + 'firstTestAttribute', + 'secondTestAttribute.nestedProperty', + ], + }, + ]; + const state = + createBlockVariationsStateWithTestBlockType( variations ); + + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: { + nestedProperty: 1, + secondNestedProperty: 10, + otherNestedProperty: 5555, + }, + secondTestAttribute: { + nestedProperty: { + firstDeeplyNestedProperty: 'a1', + secondDeeplyNestedProperty: 'a2', + otherDeeplyNestedProperty: 'ffff', + }, + }, + } ) + ).toEqual( variations[ 0 ] ); + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: { + nestedProperty: 2, + secondNestedProperty: 20, + otherNestedProperty: 5555, + }, + secondTestAttribute: { + nestedProperty: { + firstDeeplyNestedProperty: 'b1', + secondDeeplyNestedProperty: 'b2', + otherDeeplyNestedProperty: 'ffff', + }, + }, + } ) + ).toEqual( variations[ 1 ] ); + } ); it( 'should return the active variation based on the given isActive array (multiple values)', () => { const variations = [ { @@ -469,6 +630,136 @@ describe( 'selectors', () => { } ) ).toEqual( variations[ 2 ] ); } ); + it( 'should return the active variation using the match with the highest specificity for the given isActive array (multiple values)', () => { + const variations = [ + { + name: 'variation-1', + attributes: { + firstTestAttribute: 1, + secondTestAttribute: 2, + }, + isActive: [ + 'firstTestAttribute', + 'secondTestAttribute', + ], + }, + { + name: 'variation-2', + attributes: { + firstTestAttribute: 1, + secondTestAttribute: 2, + thirdTestAttribute: 3, + }, + isActive: [ + 'firstTestAttribute', + 'secondTestAttribute', + 'thirdTestAttribute', + ], + }, + { + name: 'variation-3', + attributes: { + firstTestAttribute: 1, + thirdTestAttribute: 3, + }, + isActive: [ + 'firstTestAttribute', + 'thirdTestAttribute', + ], + }, + ]; + + const state = + createBlockVariationsStateWithTestBlockType( variations ); + + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: 1, + secondTestAttribute: 2, + } ) + ).toEqual( variations[ 0 ] ); + // All variations match the following attributes. Since all matches have an array for their isActive + // fields, we can compare the specificity of each match and return the most specific match. + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: 1, + secondTestAttribute: 2, + thirdTestAttribute: 3, + } ) + ).toEqual( variations[ 1 ] ); + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: 1, + thirdTestAttribute: 3, + } ) + ).toEqual( variations[ 2 ] ); + } ); + it( 'should return the active variation using the first match given the isActive array (multiple values) and function', () => { + const variations = [ + { + name: 'variation-1', + attributes: { + firstTestAttribute: 1, + secondTestAttribute: 2, + }, + isActive: [ + 'firstTestAttribute', + 'secondTestAttribute', + ], + }, + { + name: 'variation-2', + attributes: { + firstTestAttribute: 1, + secondTestAttribute: 2, + thirdTestAttribute: 3, + }, + isActive: [ + 'firstTestAttribute', + 'secondTestAttribute', + 'thirdTestAttribute', + ], + }, + { + name: 'variation-3', + attributes: { + firstTestAttribute: 1, + thirdTestAttribute: 3, + }, + isActive: ( blockAttributes, variationAttributes ) => + blockAttributes.firstTestAttribute === + variationAttributes.firstTestAttribute && + blockAttributes.thirdTestAttribute === + variationAttributes.thirdTestAttribute, + }, + ]; + + const state = + createBlockVariationsStateWithTestBlockType( variations ); + + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: 1, + secondTestAttribute: 2, + } ) + ).toEqual( variations[ 0 ] ); + // All variations match the following attributes. However, since the third variation has a function + // for its isActive field, we cannot compare the specificity of each match, so instead we return the + // best match we've found. + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: 1, + secondTestAttribute: 2, + thirdTestAttribute: 3, + } ) + ).toEqual( variations[ 1 ] ); + expect( + getActiveBlockVariation( state, blockName, { + firstTestAttribute: 1, + thirdTestAttribute: 3, + } ) + ).toEqual( variations[ 2 ] ); + } ); it( 'should ignore attributes that are not defined in the block type', () => { const variations = [ { diff --git a/packages/blocks/src/store/utils.js b/packages/blocks/src/store/utils.js index 64974bd8000b27..ce9b9f9ab3be94 100644 --- a/packages/blocks/src/store/utils.js +++ b/packages/blocks/src/store/utils.js @@ -18,3 +18,33 @@ export const getValueFromObjectPath = ( object, path, defaultValue ) => { } ); return value ?? defaultValue; }; + +function isObject( candidate ) { + return ( + typeof candidate === 'object' && + candidate.constructor === Object && + candidate !== null + ); +} + +/** + * Determine whether a set of object properties matches a given object. + * + * Given an object of block attributes and an object of variation attributes, + * this function checks recursively whether all the variation attributes are + * present in the block attributes object. + * + * @param {Object} blockAttributes The object to inspect. + * @param {Object} variationAttributes The object of property values to match. + * @return {boolean} Whether the block attributes match the variation attributes. + */ +export function matchesAttributes( blockAttributes, variationAttributes ) { + if ( isObject( blockAttributes ) && isObject( variationAttributes ) ) { + return Object.entries( variationAttributes ).every( + ( [ key, value ] ) => + matchesAttributes( blockAttributes?.[ key ], value ) + ); + } + + return blockAttributes === variationAttributes; +} diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index 67ba56321abbc4..bace42530c0ddc 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 6.1.0 (2024-06-15) + +## 6.0.0 (2024-05-31) + +### Breaking Changes + +- Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). + ## 5.41.0 (2024-05-16) ## 5.40.0 (2024-05-02) diff --git a/packages/browserslist-config/README.md b/packages/browserslist-config/README.md index 0ab8e724947d7a..2376c8332a2b81 100644 --- a/packages/browserslist-config/README.md +++ b/packages/browserslist-config/README.md @@ -10,7 +10,7 @@ Install the module $ npm install browserslist @wordpress/browserslist-config --save-dev ``` -**Note**: This package requires Node.js 14.0.0 or later. It is not compatible with older versions. +**Note**: This package requires Node.js version with long-term support status (check [Active LTS or Maintenance LTS releases](https://nodejs.org/en/about/previous-releases)). It is not compatible with older versions. ## Usage diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 1795bc91bbc92a..67fa4e317b051c 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "5.41.0", + "version": "6.1.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,7 +20,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "engines": { - "node": ">=14" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "files": [ "index.js" diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index 53feb34d45faac..06f4e960181017 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 1.1.0 (2024-06-15) + +## 1.0.0 (2024-05-31) + +### Breaking Changes + +- Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). + ## 0.29.0 (2024-05-16) ### Internal diff --git a/packages/commands/package.json b/packages/commands/package.json index be0ce7d7b7e989..88c18afa09e6a7 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "0.29.0", + "version": "1.1.0", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,7 +20,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "engines": { - "node": ">=12" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 7ada9626797d45..f8cef47b13d199 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,9 +2,46 @@ ## Unreleased +## 28.1.0 (2024-06-15) + ### Enhancements +- Add `text-wrap: balance` fallback to all instances of `text-wrap: pretty` for greater cross browser compatibility. ([#62233](https://github.com/WordPress/gutenberg/pull/62233)) +- Updates the space between input + label to `8px` in CheckboxControl and RadioControl. Also increased the space between RadioControl components to `12px` to make it consistent with CheckboxControl. ([#61696](https://github.com/WordPress/gutenberg/pull/61696)) +- `DateTimePicker`: Replaced `ButtonGroup` with `ToggleGroupControl` component for "AM/PM" selector in DateTime component ([#61562](https://github.com/WordPress/gutenberg/pull/61562)). + +### Bug Fixes + +- `Tabs`: Prevent accidental overflow in indicator ([#61979](https://github.com/WordPress/gutenberg/pull/61979)). + +## 28.0.0 (2024-05-31) + +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). +- Increase the minimum required Node.js version to v18.12.0 matching long-term support releases ([#31270](https://github.com/WordPress/gutenberg/pull/61930)). Learn more about [Node.js releases](https://nodejs.org/en/about/previous-releases). + +### Enhancements + +- `Tabs`: Animate indicator ([#60560](https://github.com/WordPress/gutenberg/pull/60560)). - `ComboboxControl`: Introduce Combobox expandOnFocus prop ([#61705](https://github.com/WordPress/gutenberg/pull/61705)). +- `ProgressBar`: Expose as public API ([#61062](https://github.com/WordPress/gutenberg/pull/61062)). +- `ProgressBar`: Simplify default width implementation and make it more easily overridable ([#61976](https://github.com/WordPress/gutenberg/pull/61976)). +- Replaced `ButtonGroup` with `ToggleGroupControl` component for "AM/PM" selector in DateTime component ([#61562](https://github.com/WordPress/gutenberg/pull/61562)). + +### Bug Fixes + +- `Autocomplete`: Stabilize rendering of autocomplete items ([#61877](https://github.com/WordPress/gutenberg/pull/61877)). +- `TabPanel`: Make the the focus styles consistent with `Tabs`. ([#61317](https://github.com/WordPress/gutenberg/pull/61317)). +- `InputControl`: Fixed z-index issue where slider dots appeared in front of the Appearance dropdown. ([#61937](https://github.com/WordPress/gutenberg/pull/61937)) + +### Internal + +- Remove `reduceMotion` util ([#61963](https://github.com/WordPress/gutenberg/pull/61963)). +- Add type support for CSS Custom Properties ([#61872](https://github.com/WordPress/gutenberg/pull/61872)). +- Remove usage of deprecated spreading of `key` prop in JSX in CustomSelectControl and FormTokenField components ([#61692](https://github.com/WordPress/gutenberg/pull/61692)). +- `Tooltip`: Fix Ariakit tooltip store usage ([#61858](https://github.com/WordPress/gutenberg/pull/61858)). +- `CustomSelectControlV2`: Use `InputBase` for styling ([#60261](https://github.com/WordPress/gutenberg/pull/60261)). ## 27.6.0 (2024-05-16) @@ -16,6 +53,7 @@ ### Enhancements +- `FontSizePicker`: Add `vw` and `vh` units to the default units in the font size picker ([#60507](<(https://github.com/WordPress/gutenberg/pull/60607)>). - `PaletteEdit`: Use consistent spacing and metrics. ([#61368](https://github.com/WordPress/gutenberg/pull/61368)). - `FormTokenField`: Hide label when not defined ([#61336](https://github.com/WordPress/gutenberg/pull/61336)). - `ComboboxControl`: supports disabled items ([#61294](https://github.com/WordPress/gutenberg/pull/61294)). diff --git a/packages/components/package.json b/packages/components/package.json index e13a600c34feb3..e5eaf231655091 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "27.6.0", + "version": "28.1.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -19,7 +19,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "engines": { - "node": ">=12" + "node": ">=18.12.0", + "npm": ">=8.19.2" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/components/src/animate/index.tsx b/packages/components/src/animate/index.tsx index e7aaf72d243801..e819020447bc9e 100644 --- a/packages/components/src/animate/index.tsx +++ b/packages/components/src/animate/index.tsx @@ -23,7 +23,7 @@ function getDefaultOrigin( type?: GetAnimateOptions[ 'type' ] ) { */ export function getAnimateClassName( options: GetAnimateOptions ) { if ( options.type === 'loading' ) { - return clsx( 'components-animate__loading' ); + return 'components-animate__loading'; } const { type, origin = getDefaultOrigin( type ) } = options; diff --git a/packages/components/src/animate/stories/index.story.tsx b/packages/components/src/animate/stories/index.story.tsx index be076b4e6976c6..de28495a97fc57 100644 --- a/packages/components/src/animate/stories/index.story.tsx +++ b/packages/components/src/animate/stories/index.story.tsx @@ -27,7 +27,12 @@ export const Default = Template.bind( {} ); Default.args = { children: ( { className } ) => ( -

{ `No default animation. Use one of type = "appear", "slide-in", or "loading".` }

+

+ { /* eslint-disable react/no-unescaped-entities */ } + No default animation. Use one of type = "appear", "slide-in", or + "loading". + { /* eslint-enable react/no-unescaped-entities */ } +

), }; diff --git a/packages/components/src/autocomplete/autocompleter-ui.tsx b/packages/components/src/autocomplete/autocompleter-ui.tsx index a3e3cb503c483e..a22e197decd154 100644 --- a/packages/components/src/autocomplete/autocompleter-ui.tsx +++ b/packages/components/src/autocomplete/autocompleter-ui.tsx @@ -27,10 +27,57 @@ import { VisuallyHidden } from '../visually-hidden'; import { createPortal } from 'react-dom'; import type { AutocompleterUIProps, KeyedOption, WPCompleter } from './types'; +type ListBoxProps = { + items: KeyedOption[]; + onSelect: ( option: KeyedOption ) => void; + selectedIndex: number; + instanceId: number; + listBoxId: string | undefined; + className?: string; + Component?: React.ElementType; +}; + +function ListBox( { + items, + onSelect, + selectedIndex, + instanceId, + listBoxId, + className, + Component = 'div', +}: ListBoxProps ) { + return ( + + { items.map( ( option, index ) => ( + + ) ) } + + ); +} + export function getAutoCompleterUI( autocompleter: WPCompleter ) { - const useItems = autocompleter.useItems - ? autocompleter.useItems - : getDefaultUseItems( autocompleter ); + const useItems = + autocompleter.useItems ?? getDefaultUseItems( autocompleter ); function AutocompleterUI( { filterValue, @@ -62,7 +109,7 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) { // If the popover is rendered in a different document than // the content, we need to duplicate the options list in the // content document so that it's available to the screen - // readers, which check the DOM ID based aira-* attributes. + // readers, which check the DOM ID based aria-* attributes. setNeedsA11yCompat( node.ownerDocument !== contentRef.current.ownerDocument ); @@ -124,38 +171,6 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) { return null; } - const ListBox = ( { - Component = 'div', - }: { - Component?: React.ElementType; - } ) => ( - - { items.map( ( option, index ) => ( - - ) ) } - - ); - return ( <> - + { contentRef.current && needsA11yCompat && createPortal( - , + , contentRef.current.ownerDocument.body ) } diff --git a/packages/components/src/autocomplete/test/index.tsx b/packages/components/src/autocomplete/test/index.tsx index 7a31680b3dd5e1..0df784f8367cdd 100644 --- a/packages/components/src/autocomplete/test/index.tsx +++ b/packages/components/src/autocomplete/test/index.tsx @@ -67,10 +67,10 @@ describe( 'AutocompleterUI', () => { return (
{} } onSelect={ () => {} } diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index f093502a750fd7..063a2cc191eaed 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -183,7 +183,7 @@ export function Button( props ) { accessible accessibilityLabel={ label } accessibilityStates={ states } - accessibilityRole={ 'button' } + accessibilityRole="button" accessibilityHint={ hint } onPress={ onClick } style={ containerStyle } diff --git a/packages/components/src/button/index.tsx b/packages/components/src/button/index.tsx index b60f6af039e595..e2ef34f35712e7 100644 --- a/packages/components/src/button/index.tsx +++ b/packages/components/src/button/index.tsx @@ -174,14 +174,16 @@ export function UnforwardedButton( const anchorProps: ComponentPropsWithoutRef< 'a' > = Tag === 'a' ? { href, target } : {}; + const disableEventProps: { + [ key: string ]: ( event: MouseEvent ) => void; + } = {}; if ( disabled && isFocusable ) { // In this case, the button will be disabled, but still focusable and // perceivable by screen reader users. buttonProps[ 'aria-disabled' ] = true; anchorProps[ 'aria-disabled' ] = true; - for ( const disabledEvent of disabledEventsOnDisabledButton ) { - additionalProps[ disabledEvent ] = ( event: MouseEvent ) => { + disableEventProps[ disabledEvent ] = ( event: MouseEvent ) => { if ( event ) { event.stopPropagation(); event.preventDefault(); @@ -234,6 +236,7 @@ export function UnforwardedButton( ) } + { ...disableEventProps } { ...commonProps } > { elementChildren } @@ -242,6 +245,7 @@ export function UnforwardedButton(