diff --git a/.eslintrc.js b/.eslintrc.js index 9ac141fd09a04c..0b0c71c39a2664 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -76,6 +76,11 @@ const restrictedImports = [ message: "edit-widgets is a WordPress top level package that shouldn't be imported into other packages", }, + { + name: 'classnames', + message: + "Please use `clsx` instead. It's a lighter and faster drop-in replacement for `classnames`.", + }, ]; module.exports = { diff --git a/.github/setup-node/action.yml b/.github/setup-node/action.yml index af71abd3cd9b24..a17adfe5f50071 100644 --- a/.github/setup-node/action.yml +++ b/.github/setup-node/action.yml @@ -32,8 +32,15 @@ runs: - name: Install npm dependencies if: ${{ steps.cache-node_modules.outputs.cache-hit != 'true' }} - run: npm ci + run: | + npm ci shell: bash + - name: Upload npm logs as an artifact on failure + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: failure() + with: + name: npm-logs + path: C:\npm\cache\_logs # On cache hit, we run the post-install script to match the native `npm ci` behavior. # An example of this is to patch `node_modules` using patch-package. diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index d1621ed5106aab..149faee274206e 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: token: ${{ secrets.GUTENBERG_TOKEN }} show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -165,7 +165,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: path: main ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Checkout (for publishing) - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 8eafe4267bc433..1065421044373b 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: fetch-depth: 1 show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/check-components-changelog.yml b/.github/workflows/check-components-changelog.yml index 77fdf4759f7de3..d995d641fae57d 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 2b935469264800..7c26cb6e14e760 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -24,7 +24,7 @@ jobs: os: ['macos-latest', 'ubuntu-latest', 'windows-latest'] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index c919e733360ffe..16680038e0db64 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -27,7 +27,7 @@ jobs: totalParts: [8] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -53,35 +53,61 @@ jobs: - name: Archive debug artifacts (screenshots, traces) uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - if: always() + if: ${{ !cancelled() }} with: - name: failures-artifacts + name: failures-artifacts--${{ matrix.part }} path: artifacts/test-results if-no-files-found: ignore - name: Archive flaky tests report uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - if: always() + if: ${{ !cancelled() }} with: - name: flaky-tests-report + name: flaky-tests-report--${{ matrix.part }} path: flaky-tests if-no-files-found: ignore + merge-artifacts: + name: Merge Artifacts + if: ${{ !cancelled() }} + needs: [e2e-playwright] + runs-on: ubuntu-latest + outputs: + has-flaky-test-report: ${{ !!steps.merge-flaky-tests-reports.outputs.artifact-id }} + steps: + - name: Merge failures artifacts + uses: actions/upload-artifact/merge@v4 + # Don't fail the job if there aren't any artifacts to merge. + continue-on-error: true + with: + name: failures-artifacts + # Retain the merged artifacts in case of a rerun. + pattern: failures-artifacts* + delete-merged: true + + - name: Merge flaky tests reports + id: merge-flaky-tests-reports + uses: actions/upload-artifact/merge@v4 + continue-on-error: true + with: + name: flaky-tests-report + pattern: flaky-tests-report* + delete-merged: true + report-to-issues: name: Report to GitHub - needs: [e2e-playwright] - if: ${{ always() }} + needs: [merge-artifacts] + if: ${{ needs.merge-artifacts.outputs.has-flaky-test-report == 'true' }} runs-on: ubuntu-latest 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: ref: trunk show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - uses: actions/download-artifact@v4.1.7 - id: download_artifact # Don't fail the job if there isn't any flaky tests report. continue-on-error: true with: @@ -89,16 +115,13 @@ jobs: path: flaky-tests - name: Setup Node.js and install dependencies - if: ${{ steps.download_artifact.outcome == 'success' }} uses: ./.github/setup-node - name: Npm build - if: ${{ steps.download_artifact.outcome == 'success' }} # TODO: We don't have to build the entire project, just the action itself. run: npm run build:packages - name: Report flaky tests - if: ${{ steps.download_artifact.outcome == 'success' }} uses: ./packages/report-flaky-tests with: repo-token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index bcd0fee6453fdb..633f62d5ed28c9 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 b78ff9532c22d3..c5c8848026d986 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -32,7 +32,7 @@ jobs: WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 ba34e0d806185e..3b813c8f8d48fb 100644 --- a/.github/workflows/php-changes-detection.yml +++ b/.github/workflows/php-changes-detection.yml @@ -10,7 +10,7 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} steps: - name: Check out code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: fetch-depth: 0 show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 11dfdb878ef289..94397afd7b4bca 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: path: cli ref: trunk @@ -39,7 +39,7 @@ jobs: - name: Checkout (for publishing) if: ${{ github.event.inputs.release_type != 'wp' }} - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 05d28f888d0ae0..099203bbffe720 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 5d1d476226b12f..a4dce407d1c0ff 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 409ba9f8fe1717..516f783c11e401 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: ruby/setup-ruby@1198b074305f9356bd56dd4b311757cc0dab2f1c # v1.175.1 + - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.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 12c8931efca066..ff3fe96d505f6f 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 56b7471f06d9b3..7486ea32533e6a 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 22bca2dc78186b..a4a639e183d5bf 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -70,7 +70,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -170,7 +170,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -281,7 +281,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} @@ -351,7 +351,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 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 8a92d0443d577a..8f57a749b0601d 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -96,7 +96,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: ref: ${{ matrix.branch }} token: ${{ secrets.GUTENBERG_TOKEN }} diff --git a/README.md b/README.md index 360b2851be092b..9c920337ef5941 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Get hands on: check out the [block editor live demo](https://wordpress.org/guten Extending and customizing is at the heart of the WordPress platform, this is no different for the Gutenberg project. The editor and future products can be extended by third-party developers using plugins. -Review the [Create a Block tutorial](/docs/getting-started/devenv/get-started-with-create-block.md) for the fastest way to get started extending the block editor. See the [Developer Documentation](https://developer.wordpress.org/block-editor/#develop-for-the-block-editor) for extensive tutorials, documentation, and API references. +Review the [Quick Start Guide](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/) for the fastest way to get started extending the block editor. See the [Block Editor Handbook](https://developer.wordpress.org/block-editor/) for extensive tutorials, documentation, and API references. Also, check the [WordPress Developer Blog](https://developer.wordpress.org/blog/) for great articles about block development, among other topics. ### Contribute to Gutenberg diff --git a/bin/test-create-block.sh b/bin/test-create-block.sh index 7959334a8e30ec..99b7e8e6082604 100755 --- a/bin/test-create-block.sh +++ b/bin/test-create-block.sh @@ -69,7 +69,7 @@ status "Building block..." ../node_modules/.bin/wp-scripts build status "Verifying build..." -expected=7 +expected=9 actual=$( find build -maxdepth 1 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`build\` directory, but found $actual." diff --git a/changelog.txt b/changelog.txt index aee9a5c1a15753..0bb052d82feb0b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,6 @@ == Changelog == -= 18.3.0-rc.1 = - += 18.3.0 = ## Changelog @@ -233,6 +232,8 @@ The following contributors merged PRs in this release: @aaronrobertshaw @afercia @ajlende @carolinan @cbravobernal @colorful-tones @DaniGuardiola @desrosj @draganescu @ellatrix @fabiankaegy @fullofcaffeine @geriux @huubl @itzmekhokan @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @juanfra @juanmaguitar @lanresmith @MaggieCabrera @Mamaduka @mirka @ntsekouras @oandregal @ockham @ramonjd @retrofox @richtabor @SantosGuillamot @scruffian @shail-mehta @sirreal @stokesman @sunil25393 @swissspidy @t-hamano @talldan @twstokes @tyxla @youknowriad + + = 18.2.0 = ## Changelog diff --git a/docs/contributors/code/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md index 63114073b1b802..06f86715a65a06 100644 --- a/docs/contributors/code/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -39,13 +39,13 @@ Components may be assigned with class names that indicate states (for example, a **Example:** -Consider again the Notices example. We may want to apply specific styling for dismissible notices. The [`classnames` package](https://www.npmjs.com/package/classnames) can be a helpful utility for conditionally applying modifier class names. +Consider again the Notices example. We may want to apply specific styling for dismissible notices. The [`clsx` package](https://www.npmjs.com/package/clsx) can be a helpful utility for conditionally applying modifier class names. ```jsx -import classnames from 'classnames'; +import clsx from 'clsx'; export default function Notice( { children, onRemove, isDismissible } ) { - const classes = classnames( 'components-notice', { + const classes = clsx( 'components-notice', { 'is-dismissible': isDismissible, } ); diff --git a/docs/contributors/code/e2e/README.md b/docs/contributors/code/e2e/README.md index 43443cddd6aeb6..3a123cc2988b7a 100644 --- a/docs/contributors/code/e2e/README.md +++ b/docs/contributors/code/e2e/README.md @@ -36,6 +36,7 @@ xvfb-run npm run test:e2e # Only run webkit tests. xvfb-run -- npm run test:e2e -- --project=webkit ``` +If you're already editing in VS Code, you may find the [Playwright extension](https://playwright.dev/docs/getting-started-vscode) helpful for running, writing and debugging tests. ## Best practices diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 57a379a9fafe13..2bc7fc8f0bf8be 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -224,6 +224,11 @@ This is the canonical list of keyboard shortcuts: / / + + Create a group block from the selected multiple blocks. + Ctrl+G + G + Remove multiple selected blocks. delbackspace diff --git a/docs/manifest.json b/docs/manifest.json index 05f1f36ce7ca63..a2577530463555 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -509,6 +509,18 @@ "markdown_source": "../docs/reference-guides/interactivity-api/api-reference.md", "parent": "interactivity-api" }, + { + "title": "About the Interactivity API", + "slug": "iapi-about", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-about.md", + "parent": "interactivity-api" + }, + { + "title": "Frequently Asked Questions", + "slug": "iapi-faq", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-faq.md", + "parent": "interactivity-api" + }, { "title": "SlotFills Reference", "slug": "slotfills", diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 3a0a88048e982f..c08869db34b484 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -415,7 +415,7 @@ Create a bulleted or numbered list. ([Source](https://github.com/WordPress/guten - **Name:** core/list - **Category:** text - **Allowed Blocks:** core/list-item -- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~, ~~html~~ +- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** ordered, placeholder, reversed, start, type, values ## List item diff --git a/docs/reference-guides/interactivity-api/README.md b/docs/reference-guides/interactivity-api/README.md index 9a9e5dd02d4311..b6e0d639c3fc8b 100644 --- a/docs/reference-guides/interactivity-api/README.md +++ b/docs/reference-guides/interactivity-api/README.md @@ -9,7 +9,7 @@ Blocks can share data, actions, and callbacks between them. This makes communica For more information about the genesis of the Interactivity API, check out the [original proposal](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/). You can also review the [merge announcement](https://make.wordpress.org/core/2024/02/19/merge-announcement-interactivity-api/), the [status update post](https://make.wordpress.org/core/2023/08/15/status-update-on-the-interactivity-api/), and the official [Trac ticket](https://core.trac.wordpress.org/ticket/60356).
- The Interactivity API is supported by the package @wordpress/interactivity which is bundled in WordPress Core from v6.5 + The Interactivity API is supported by the package @wordpress/interactivity which is bundled in WordPress Core from v6.5
## Navigating the Interactivity API docs @@ -22,6 +22,12 @@ Use the following links to locate the topic you're interested in. If you have ne - **[API Reference](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/):** To take a deep dive into how the API works internally, the list of Directives, and how the Store works. - **[Docs and Examples](#docs-examples):** Additional resources to learn/read more about the Interactivity API. +To get a deeper understanding of what the Interactivity API is or find answers to questions you may have about this standard, check the following resources: + +- **[About the Interactivity API](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-about/):** To learn more about the API Goals and the reasoning behind the use of a standard to add interactivity to blocks. +- **[Frequently Asked Questions](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-faq/):** To find responses to some frequently asked questions about the technology behind and alternatives. + + ## Requirements of the Interactivity API Interactivity API is included in Core in WordPress 6.5. For versions below, you'll need Gutenberg 17.5 or higher installed and activated in your WordPress installation. @@ -51,7 +57,7 @@ Refer to the [`interactivity` support property docs](https://developer.wordpress #### 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: +The Interactivity API provides the `@wordpress/interactivity` Script Module. JavaScript using the Interactivity API should be implemented as Script Modules so they can depend on `@wordpress/interactivity`. [Script Modules have been available since WordPress 6.5](https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/). Blocks can use [`viewScriptModule` block metadata](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script-module) to enqueue their Script Modules easily: ```json // block.json @@ -61,6 +67,19 @@ The Interactivity API provides the `@wordpress/interactivity` Script Module. Jav } ``` +The use of `viewScriptModule` also requires the `--experimental-modules` flag for both the [`build`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#build) and [`start`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#start) scripts of `wp-scripts` to ensure a proper build of the Script Modules. + + +```json +// package.json +{ + "scripts": { + ... + "build": "wp-scripts build --experimental-modules", + "start": "wp-scripts start --experimental-modules" + } +``` + #### 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. diff --git a/docs/reference-guides/interactivity-api/iapi-about.md b/docs/reference-guides/interactivity-api/iapi-about.md new file mode 100644 index 00000000000000..887a284be298fb --- /dev/null +++ b/docs/reference-guides/interactivity-api/iapi-about.md @@ -0,0 +1,195 @@ +# About the Interactivity API + +The Interactivity API is **a [standard](#why-a-standard) system of [directives](#why-directives), based on declarative code, for [adding frontend interactivity to blocks](#api-goals)**. + +**Directives extend HTML with special attributes** that tell the Interactivity API to attach a specified behavior to a DOM element or even to transform it. For those familiar with [Alpine.js](https://alpinejs.dev/), it’s a similar approach but explicitly designed to work seamlessly with WordPress. + +## API Goals + +The main goal of the Interactivity API is to **provide a standard and simple way to handle the frontend interactivity of Gutenberg blocks**. + +A standard makes it **easier for developers to create rich, interactive user experiences**, from simple cases like counters or popups to more complex features like instant page navigation, instant search, or carts and checkouts. + +All these user experiences are technically possible right now without the Interactivity API. However, the more complex the user experience and the more blocks interact with each other, the harder it becomes for developers to build and maintain sites. There are a lot of challenges they have to figure out themselves. The API aims to provide out-of-the-box means for supporting these kinds of interactions. + +To address this challenge the following requirements/goals for the Interactivity API were defined: + +- **Block-first and PHP-first**: The API must work well with PHP and the current block system, including dynamic blocks, widely extended in WordPress. It must support server-side rendering. Server-rendered HTML and client-hydrated HTML must be exactly the same. This is important for SEO and the user experience. +- **Backward compatible**: The API must be compatible with WordPress hooks, which could, for example, modify server-rendered HTML. It must also be compatible with internationalization and existing JS libraries on the site (such as jQuery). +- **Optional and gradual adoption**: Related to the previous point, the API must remain optional. It should be possible to adopt it gradually, meaning that interactive blocks not using this API can coexist with those using it. +- **Declarative and reactive**: The API must use declarative code, listen to changes in the data, and update only the parts of the DOM that depend on that data. +- **Performant**: The runtime must be fast and lightweight to ensure the best user experience. +- **Extensible**: In the same way WordPress focuses on extensibility, this new system must provide extensibility patterns to cover most use cases. +- **Atomic and composable**: Having small reusable parts that can be combined to create more complex systems is required to create flexible and scalable solutions. +- **Compatible with the existing block development tooling**: The API must be integrated with the existing block-building tools without requiring additional tooling or configuration by the developer. + +Apart from all these requirements, integrating **client-side navigation** on top of any solution should be easy and performant. Client-side navigation is the process of navigating between site pages without reloading the entire page, which is one of the most impressive user experiences demanded by web developers. For that reason, this functionality should be compatible with this new system. + +## Why directives? + +Directives are the result of deep [research into different possibilities and approaches](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-faq/#what-approaches-have-been-considered-instead-of-using-directives). We’ve found that this design covers the requirements most effectively. + +### Block-first and PHP-friendly + +The API is designed for the world of blocks and takes WordPress history of being closely attached to web standards to heart. + +As directives are HTML attributes, they are perfect for dynamic blocks and PHP. + +_Dynamic block example_ +```html +
false ) ); ?> + data-wp-watch="callbacks.logIsOpen" +> + + +

+ This element is now visible! +

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

This element is now visible!

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

+ This element is now visible! +

+
+``` + +Using imperative code may be easier when creating simple user experiences, but it becomes much more difficult as applications become more complex. The Interactivity API must cover all use cases, from the simplest to the most challenging. That’s why a declarative approach using directives better fits the Interactivity API. + +### Performant + +The API has been designed to be as performant as possible: + +- **The runtime code needed for the directives is just ~10 KB**, and it only needs to be loaded once for all the blocks. +- **The scripts will load without blocking the page rendering**. + +### Extensible + +Directives can be added, removed, or modified directly from the HTML. For example, users could use the [`render_block` filter](https://developer.wordpress.org/reference/hooks/render_block/) to modify the HTML and its behavior. + +### Atomic and composable + +Each directive controls a small part of the DOM, and you can combine multiple directives to create rich, interactive user experiences. + +### Compatible with the existing block development tooling + +The API works out of the box with standard block-building tools like [`wp-scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/). The only requirement for `wp-scripts` to properly build the [Script Modules](https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/) using the Interactivity API is the use of the --experimental-modules flag for both [`build`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#build) and [`start`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#start) scripts. + +### Client-side navigation + +The Interactivity API comes with built-in primitives for adding client-side navigation to your site. This functionality is completely optional, but it opens the possibility to create these user experiences without having to opt out of the WordPress rendering system. + +
+ Full-page client-side navigation with the Interactivity API is still a work in progress (see #60951). Still, it is expected that all the interactive blocks will have to use the Interactivity API to enable full-page client-side navigation with the Interactivity API. Only in this case, the Interactivity API won't be fully compatible with other libraries (such as jQuery). +
+ +It also pairs very well with the [View Transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) allowing developers to animate page transitions easily. + +## Why a standard? + +Blocks using the Interactivity API and interactive blocks using other approaches like jQuery can coexist, and everything will work as expected. However, the Interactivity API comes with some benefits for your interactive blocks: + +- **Blocks can communicate with each other easily**. With a standard, this communication is handled by default. When different blocks use different approaches to frontend interactivity, inter-block communication becomes more complex and almost impossible when different developers create blocks. +- **Composability and compatibility**: You can combine interactive blocks, and nest them in structures with defined behaviors. Thanks to following the same standard, they are fully cross-compatible. If each block used a different approach to interactivity, they would likely break. +- **Fewer KBs will be sent to the browser**. If each plugin author uses a different JS framework, more code will be loaded in the front end. If all the blocks use the same one, the code is reused. +- If all the blocks on a page use this standard, **site-wide features like client-side navigation can be enabled**. + +Additionally, with a standard, **WordPress can absorb the maximum amount of complexity from the developer** because it will handle most of what’s needed to create an interactive block. + +_Complexities absorbed by the standard_ + +Two columns table comparing some aspects with and without a standard. Without a standard, block developers have to take care of everything, while having a standard. Totally handled by the standard: Tooling, hydration, integrating it with WordPress, SSR of the interactive parts, inter-block communication, and frontend performance. Partially handled: Security, accessibility, and best practices. Developer responsibility: Block logic. In the without a standard column, everything is under the developer responsability. + + +With this absorption, less knowledge is required to create interactive blocks, and developers have fewer decisions to worry about. + +By adopting a standard, learning from other interactive blocks is simpler, and fosters collaboration and code reusability. As a result, the development process is leanier and friendlier to less experienced developers. + diff --git a/docs/reference-guides/interactivity-api/iapi-faq.md b/docs/reference-guides/interactivity-api/iapi-faq.md new file mode 100644 index 00000000000000..fba3603f39169e --- /dev/null +++ b/docs/reference-guides/interactivity-api/iapi-faq.md @@ -0,0 +1,127 @@ +# Frequently Asked Questions + +## How does the Interactivity API work under the hood? + +Its three main components are: + +- [Preact](https://preactjs.com/) combined with [Preact Signals](https://preactjs.com/guide/v10/signals/) for hydration, client logic, and client-side navigation. +- HTML Directives that can be understood by both the client and server. +- Server-side logic, handled by the [HTML_Tag_Processor](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/). + +## Why Preact to build the directives system? Why not React or another JavaScript framework? + +Preact has a number of advantages over React and other JavaScript frameworks like Vue, Svelte, or Solid in the context of the frontend (which is the focus of the Interactivity API): + +- It’s small: 8kB, including [hooks](https://preactjs.com/guide/v10/hooks/) and [signals](https://preactjs.com/blog/introducing-signals/). +- It gives us DOM diffing out of the box. +- It’s extremely extensible through their Option Hooks. They use that extensibility for the hooks (preact/hooks), compatibility with React (preact/compat) and their signals (@preact/signals). Basically, everything but the DOM diffing algorithm. +- Its core team has been great and very helpful. They are also interested in enhancing this “island-based” usage of Preact. + +## Is Gutenberg going to move from React to Preact since the Interactivity API uses it? + +No. At the moment, there are no plans to make that transition. The requirements and advantages of the editor, as a fully interactive application, are quite different. Preact does have a [`@preact/compat`](https://preactjs.com/guide/v10/switching-to-preact/) package that enables full compatibility with the React ecosystem, and many large web applications use it. However, using Preact in the block editor would not offer advantages like it does on the frontend in the Interactivity API. + +## What approaches have been considered instead of using directives? + +Many alternative approaches were considered. Here’s a brief summary of some of them: + +### React and other JavaScript frameworks + +React was considered first because Gutenberg developers are familiar with it. Other popular JS frameworks like Svelte, Vue.js, or Angular were also considered, but none of them (including React) are PHP-friendly or compatible with WordPress hooks or internationalization. + +### Alpine.js + +Alpine.js is a great framework, and it inspired a lot of functionality in the Interactivity API. However, it doesn’t support server-side rendering of its [directives](https://github.com/alpinejs/alpine/tree/d7f9d641f7a763c56c598d118bd189a406a22383/packages/docs/src/en/directives), and having a similar system tailored for WordPress blocks has many benefits. + +### Plain JavaScript + +See the answer below. + +### Template DSL + +The possibility of creating a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for writing interactive templates was also researched. The code written in that Template DSL would then be compiled into both JavaScript and PHP. However, creating a production-grade Template compiler is complex and would be a large and risky investment of effort. This approach is still being considered for the future, with the directives serving as a compilation target. + +## Why should I, as a block developer, use the Interactivity API rather than React? + +Using React on the frontend doesn’t work smoothly with server rendering in PHP. Every approach that uses React to render blocks has to load content using client-side JavaScript. If you only render your blocks on the client, it typically results in a poor user experience because the user stares at empty placeholders and spinners while waiting for content to load. + +Using JS in PHP extensions (like v8js) is also possible, but unfortunately PHP extensions are not backward compatible and can only be used when there's a PHP fallback. + +Now, it’s possible to server-render a block in PHP **and** use React to render the same block on the frontend. However, this results in a poor developer experience because the logic has to be duplicated across the PHP and React parts. Not only that, but you have now exposed yourself to subtle bugs caused by WordPress hooks! + +Imagine installing a third-party plugin with a hook (filter) that modifies the server-rendered HTML. Let’s say this filter adds a single CSS class to your block’s HTML. That CSS class will be present in the server-rendered markup. On the frontend, your block will render again in React, but now the content will not include that CSS class because there is no way to apply WordPress hooks to React-rendered content! + +On the other hand, the Interactivity API is designed to work perfectly with WordPress hooks because directives enhance the server-rendered HTML with behaviors. This also means it works out of the box with WordPress backend APIs like i18n. + +To summarize, using the Interactivity API rather than just using React comes with these benefits: + +- If you use React, your interactive blocks must generate the same markup on the client as they do on the server in PHP. Using the Interactivity API, there is no such requirement as directives are added to server-rendered HTML. +- The Interactivity API is PHP-friendlier. It works out of the box with WordPress hooks or other server functionalities such as internationalization. For example, with React, you can’t know which hooks are applied on the server, and their modifications would be overwritten after hydration. +- All the benefits of [using a standard](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/what-is-interactivity-api#why-a-standard). + + +## What are the benefits of Interactivity API over just using jQuery or vanilla JavaScript? + +The main difference is that the Interactivity API is **declarative and reactive**, so writing and maintaining complex interactive experiences should become way easier. Additionally, it has been **specially designed to work with blocks**, providing a standard that comes with the benefits mentioned above, like inter-block communication, compatibility, or site-wide features such as client-side navigation. + +Finally, comparing it with jQuery, **the Interactivity API runtime is ~10kb**, which is much more lightweight. Actually, there is an ongoing effort to remove heavy frameworks like jQuery across the WordPress ecosystem, and this would help in this regard. + +## Do I need to know React, PHP, and this Interactivity API? + +If you want to add frontend interactivity to your blocks using this API, the short answer is yes. If your block is not interactive, the block creation workflow will remain exactly the same. + +The Interactivity API introduces a new standard method to facilitate the integration of interactive behaviors into the frontend part of WordPress. This means that you still need to use React to handle the editor part of your blocks. + +On the other hand, if you want to create an interactive block, with the Interactivity API you don’t have to deal with complex topics like tooling, integration with WordPress, inter-block communication, or the server-side rendering of the interactive parts. + +## Can the Interactivity API be used beyond a block? + +Absolutely, yes, it is not limited to blocks. You'll see a lot of mentions of how the Interactivity API provides a standard for creating interactive blocks, but that's only because that's the most common use case. More generally speaking, the Interactivity API standard can be used to add "interactive behaviors" to the front end of any part of WordPress. + +See the [`wp_interactivity_process_directives` function](https://developer.wordpress.org/reference/functions/wp_interactivity_process_directives/) for details on using the Interactivity API outside of blocks with arbitrary HTML. + +## Does this mean I must migrate all my interactive blocks to use this API? + +No. Blocks outside the Interactivity API can coexist with blocks using it. However, as explained above, keep in mind that there are some benefits for blocks that use the API: + +- **Blocks can communicate with each other easily**. With a standard, this communication is handled by default. When different blocks use different approaches to frontend interactivity, inter-block communication becomes more complex and gets almost impossible when separate developers create blocks. +- **Composability and compatibility**: You can combine interactive blocks, nest them in structures with defined behaviors, and, thanks to following the same standard, they are fully cross-compatible. If each block were to use a different approach to interactivity, they would likely break. +- **Fewer KBs will be sent to the browser**. If each plugin author uses a different JS framework, more code will be loaded in the frontend. If all the blocks use the same one, the code is reused. +- If all the blocks on a page use this standard, site-wide features like client-side navigation can be enabled. + +## What are the performance implications of using this API? Is it worth loading the Interactivity API for very simple use cases? + +The API has been designed with performance in mind, so it shouldn’t be a problem: + +- **The runtime code needed for the directives is just ~10 KB**, and it only needs to be loaded once for all the blocks. +- **All the script modules that belong to the Interactivity API (including the `view.js` files) will load without blocking the page rendering.** +- There are [ongoing explorations](https://github.com/WordPress/gutenberg/discussions/52723) about the possibility of **delaying the scripts loading once the block is in the viewport**. This way, the initial load would be optimized without affecting the user experience. + +## Does it work with the Core Translation API? + +As the Interactivity API works perfectly with server-side rendering, you can use all the WordPress APIs including [`__()`](https://developer.wordpress.org/reference/functions/__/) and [`_e()`](https://developer.wordpress.org/reference/functions/_e/). You can use it to translate the text in the HTML (as you normally would) and even use it inside the store when [using `wp_interactivity_state()` on the server side](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/#setting-the-store). It might look something like this: + +```php +// render.php +wp_interactivity_state( 'favoriteMovies', array( + "1" => array( + "id" => "123-abc", + "movieName" => __("someMovieName", "textdomain") + ), +) ); +``` + +A translation API compatible with script modules (needed for the Interactivity API) is currently being worked on. Check [#60234](https://core.trac.wordpress.org/ticket/60234) to follow the progress on this work. + +## I’m concerned about XSS; can JavaScript be injected into directives? + +No. The Interactivity API only allows for [References](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/api-reference/#values-of-directives-are-references-to-store-properties) to be passed as values to the directives. This way, there is no need to eval() full JavaScript expressions, so it’s not possible to perform XSS attacks. + +## Does this work with Custom Security Policies? + +Yes. The Interactivity API does not use [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) or the [`Function()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) constructor, so it doesn’t violate the [`unsafe-eval`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#unsafe_keyword_values) content security policy. It is also designed to work with any [custom content security policy](https://developer.wordpress.org/apis/security/). + +## Can you use directives to make AJAX/REST-API requests? + +Sure. Actions and callbacks called by directives can do anything a JavaScript function can, including making API requests. + diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index ea11c0a3898838..f752fe8104a568 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -116,6 +116,8 @@ Settings related to dimensions. | Property | Type | Default | Props | | --- | --- | --- |--- | | aspectRatio | boolean | false | | +| defaultAspectRatios | boolean | true | | +| aspectRatios | array | | name, ratio, slug | | minHeight | boolean | false | | --- diff --git a/docs/toc.json b/docs/toc.json index c0ec94fd049a0d..fa80ee6c4f4404 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -209,6 +209,12 @@ }, { "docs/reference-guides/interactivity-api/api-reference.md": [] + }, + { + "docs/reference-guides/interactivity-api/iapi-about.md": [] + }, + { + "docs/reference-guides/interactivity-api/iapi-faq.md": [] } ] }, diff --git a/gutenberg.php b/gutenberg.php index dc7a76c1531ba1..bfac7d69c8bbbb 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.3.0-rc.1 + * Version: 18.3.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php index 70957897229595..8e3c06159a1201 100644 --- a/lib/block-supports/background.php +++ b/lib/block-supports/background.php @@ -53,10 +53,10 @@ function gutenberg_render_background_support( $block_content, $block ) { } $background_styles = array(); - $background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; $background_styles['backgroundImage'] = isset( $block_attributes['style']['background']['backgroundImage'] ) ? $block_attributes['style']['background']['backgroundImage'] : array(); - if ( isset( $background_styles['backgroundImage']['source'] ) && 'file' === $background_styles['backgroundImage']['source'] && isset( $background_styles['backgroundImage']['url'] ) ) { + if ( ! empty( $background_styles['backgroundImage'] ) ) { + $background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; $background_styles['backgroundPosition'] = isset( $block_attributes['style']['background']['backgroundPosition'] ) ? $block_attributes['style']['background']['backgroundPosition'] : null; $background_styles['backgroundRepeat'] = isset( $block_attributes['style']['background']['backgroundRepeat'] ) ? $block_attributes['style']['background']['backgroundRepeat'] : null; diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index e18dcba462a5ea..aac62e402148a3 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -573,112 +573,116 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { return $block_content; } - $outer_class_names = array(); - $container_content_class = wp_unique_id( 'wp-container-content-' ); - $child_layout_declarations = array(); - $child_layout_styles = array(); - - $self_stretch = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null; - - if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) { - $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize']; - $child_layout_declarations['box-sizing'] = 'border-box'; - } elseif ( 'fill' === $self_stretch ) { - $child_layout_declarations['flex-grow'] = '1'; - } + $outer_class_names = array(); - $column_start = isset( $block['attrs']['style']['layout']['columnStart'] ) ? $block['attrs']['style']['layout']['columnStart'] : null; - $column_span = isset( $block['attrs']['style']['layout']['columnSpan'] ) ? $block['attrs']['style']['layout']['columnSpan'] : null; - if ( $column_start && $column_span ) { - $child_layout_declarations['grid-column'] = "$column_start / span $column_span"; - } elseif ( $column_start ) { - $child_layout_declarations['grid-column'] = "$column_start"; - } elseif ( $column_span ) { - $child_layout_declarations['grid-column'] = "span $column_span"; - } + // Child layout specific logic. + if ( $child_layout ) { + $container_content_class = wp_unique_id( 'wp-container-content-' ); + $child_layout_declarations = array(); + $child_layout_styles = array(); - $row_start = isset( $block['attrs']['style']['layout']['rowStart'] ) ? $block['attrs']['style']['layout']['rowStart'] : null; - $row_span = isset( $block['attrs']['style']['layout']['rowSpan'] ) ? $block['attrs']['style']['layout']['rowSpan'] : null; - if ( $row_start && $row_span ) { - $child_layout_declarations['grid-row'] = "$row_start / span $row_span"; - } elseif ( $row_start ) { - $child_layout_declarations['grid-row'] = "$row_start"; - } elseif ( $row_span ) { - $child_layout_declarations['grid-row'] = "span $row_span"; - } + $self_stretch = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null; - $child_layout_styles[] = array( - 'selector' => ".$container_content_class", - 'declarations' => $child_layout_declarations, - ); + if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) { + $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize']; + $child_layout_declarations['box-sizing'] = 'border-box'; + } elseif ( 'fill' === $self_stretch ) { + $child_layout_declarations['flex-grow'] = '1'; + } - $minimum_column_width = isset( $block['parentLayout']['minimumColumnWidth'] ) ? $block['parentLayout']['minimumColumnWidth'] : null; - $column_count = isset( $block['parentLayout']['columnCount'] ) ? $block['parentLayout']['columnCount'] : null; + $column_start = isset( $block['attrs']['style']['layout']['columnStart'] ) ? $block['attrs']['style']['layout']['columnStart'] : null; + $column_span = isset( $block['attrs']['style']['layout']['columnSpan'] ) ? $block['attrs']['style']['layout']['columnSpan'] : null; + if ( $column_start && $column_span ) { + $child_layout_declarations['grid-column'] = "$column_start / span $column_span"; + } elseif ( $column_start ) { + $child_layout_declarations['grid-column'] = "$column_start"; + } elseif ( $column_span ) { + $child_layout_declarations['grid-column'] = "span $column_span"; + } - /* - * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set, - * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed - * once the grid has less columns than the start. - * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set. - * In that case, if columnCount doesn't exist, we can assume that the grid is responsive. - */ - if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) { - $column_span_number = floatval( $column_span ); - $column_start_number = floatval( $column_start ); - $highest_number = max( $column_span_number, $column_start_number ); - $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem'; - $parent_column_value = floatval( $parent_column_width ); - $parent_column_unit = explode( $parent_column_value, $parent_column_width ); + $row_start = isset( $block['attrs']['style']['layout']['rowStart'] ) ? $block['attrs']['style']['layout']['rowStart'] : null; + $row_span = isset( $block['attrs']['style']['layout']['rowSpan'] ) ? $block['attrs']['style']['layout']['rowSpan'] : null; + if ( $row_start && $row_span ) { + $child_layout_declarations['grid-row'] = "$row_start / span $row_span"; + } elseif ( $row_start ) { + $child_layout_declarations['grid-row'] = "$row_start"; + } elseif ( $row_span ) { + $child_layout_declarations['grid-row'] = "span $row_span"; + } + + $child_layout_styles[] = array( + 'selector' => ".$container_content_class", + 'declarations' => $child_layout_declarations, + ); + + $minimum_column_width = isset( $block['parentLayout']['minimumColumnWidth'] ) ? $block['parentLayout']['minimumColumnWidth'] : null; + $column_count = isset( $block['parentLayout']['columnCount'] ) ? $block['parentLayout']['columnCount'] : null; /* - * If there is no unit, the width has somehow been mangled so we reset both unit and value - * to defaults. - * Additionally, the unit should be one of px, rem or em, so that also needs to be checked. + * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set, + * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed + * once the grid has less columns than the start. + * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set. + * In that case, if columnCount doesn't exist, we can assume that the grid is responsive. */ - if ( count( $parent_column_unit ) <= 1 ) { - $parent_column_unit = 'rem'; - $parent_column_value = 12; - } else { - $parent_column_unit = $parent_column_unit[1]; + if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) { + $column_span_number = floatval( $column_span ); + $column_start_number = floatval( $column_start ); + $highest_number = max( $column_span_number, $column_start_number ); + $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem'; + $parent_column_value = floatval( $parent_column_width ); + $parent_column_unit = explode( $parent_column_value, $parent_column_width ); + + /* + * If there is no unit, the width has somehow been mangled so we reset both unit and value + * to defaults. + * Additionally, the unit should be one of px, rem or em, so that also needs to be checked. + */ + if ( count( $parent_column_unit ) <= 1 ) { + $parent_column_unit = 'rem'; + $parent_column_value = 12; + } else { + $parent_column_unit = $parent_column_unit[1]; - if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) { - $parent_column_unit = 'rem'; + if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) { + $parent_column_unit = 'rem'; + } } + + /* + * A default gap value is used for this computation because custom gap values may not be + * viable to use in the computation of the container query value. + */ + $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5; + $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value; + $container_query_value = $container_query_value . $parent_column_unit; + // If a span is set we want to preserve it as long as possible, otherwise we just reset the value. + $grid_column_value = $column_span ? '1/-1' : 'auto'; + + $child_layout_styles[] = array( + 'rules_group' => "@container (max-width: $container_query_value )", + 'selector' => ".$container_content_class", + 'declarations' => array( + 'grid-column' => $grid_column_value, + ), + ); } /* - * A default gap value is used for this computation because custom gap values may not be - * viable to use in the computation of the container query value. + * Add to the style engine store to enqueue and render layout styles. + * Return styles here just to check if any exist. */ - $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5; - $container_query_value = $highest_number * $parent_column_value + ( $highest_number - 1 ) * $default_gap_value; - $container_query_value = $container_query_value . $parent_column_unit; - // If a span is set we want to preserve it as long as possible, otherwise we just reset the value. - $grid_column_value = $column_span ? '1/-1' : 'auto'; - - $child_layout_styles[] = array( - 'rules_group' => "@container (max-width: $container_query_value )", - 'selector' => ".$container_content_class", - 'declarations' => array( - 'grid-column' => $grid_column_value, - ), + $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules( + $child_layout_styles, + array( + 'context' => 'block-supports', + 'prettify' => false, + ) ); - } - /* - * Add to the style engine store to enqueue and render layout styles. - * Return styles here just to check if any exist. - */ - $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules( - $child_layout_styles, - array( - 'context' => 'block-supports', - 'prettify' => false, - ) - ); - - if ( $child_css ) { - $outer_class_names[] = $container_content_class; + if ( $child_css ) { + $outer_class_names[] = $container_content_class; + } } // Prep the processor for modifying the block output. diff --git a/lib/blocks.php b/lib/blocks.php index eed9142db436a0..679219cc6ff774 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -26,7 +26,6 @@ function gutenberg_reregister_core_block_types() { 'form-submit-button', 'group', 'html', - 'list', 'list-item', 'missing', 'more', @@ -76,6 +75,7 @@ function gutenberg_reregister_core_block_types() { 'heading.php' => 'core/heading', 'latest-comments.php' => 'core/latest-comments', 'latest-posts.php' => 'core/latest-posts', + 'list.php' => 'core/list', 'loginout.php' => 'core/loginout', 'media-text.php' => 'core/media-text', 'navigation.php' => 'core/navigation', diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 77004253bea868..1dec7b164d880b 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -123,9 +123,19 @@ 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 Added `aspectRatios`. * @var array */ const PRESETS_METADATA = array( + array( + 'path' => array( 'dimensions', 'aspectRatios' ), + 'prevent_override' => array( 'dimensions', 'defaultAspectRatios' ), + 'use_default_names' => false, + 'value_key' => 'ratio', + 'css_vars' => '--wp--preset--aspect-ratio--$slug', + 'classes' => array(), + 'properties' => array( 'aspect-ratio' ), + ), array( 'path' => array( 'color', 'palette' ), 'prevent_override' => array( 'color', 'defaultPalette' ), @@ -310,7 +320,6 @@ class WP_Theme_JSON_Gutenberg { ), 'background-image' => array( array( 'background', 'backgroundImage', 'url' ), - array( 'background', 'backgroundImage', 'source' ), ), ); @@ -397,8 +406,10 @@ class WP_Theme_JSON_Gutenberg { ), 'custom' => null, 'dimensions' => array( - 'aspectRatio' => null, - 'minHeight' => null, + 'aspectRatio' => null, + 'aspectRatios' => null, + 'defaultAspectRatios' => null, + 'minHeight' => null, ), 'layout' => array( 'contentSize' => null, @@ -483,7 +494,7 @@ class WP_Theme_JSON_Gutenberg { * updated `blockGap` to be allowed at any level. * @since 6.2.0 Added `outline`, and `minHeight` properties. * @since 6.6.0 Added `background` sub properties to top-level only. - * + * @since 6.6.0 Added `dimensions.aspectRatio`. * @var array */ const VALID_STYLES = array( diff --git a/lib/compat/wordpress-6.4/block-hooks.php b/lib/compat/wordpress-6.4/block-hooks.php index 46115d5b6c629c..f77582caf13454 100644 --- a/lib/compat/wordpress-6.4/block-hooks.php +++ b/lib/compat/wordpress-6.4/block-hooks.php @@ -290,7 +290,7 @@ function gutenberg_register_block_hooks_rest_field() { 'block_hooks', array( 'schema' => array( - 'description' => __( 'This block is automatically inserted near any occurence of the block types used as keys of this map, into a relative position given by the corresponding value.', 'gutenberg' ), + 'description' => __( 'This block is automatically inserted near any occurrence of the block types used as keys of this map, into a relative position given by the corresponding value.', 'gutenberg' ), 'type' => 'object', 'patternProperties' => array( '^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$' => array( diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php index a695d075c0b526..89bb22825103ac 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -65,68 +65,27 @@ function gutenberg_block_bindings_replace_html( $block_content, $block_name, str switch ( $block_type->attributes[ $attribute_name ]['source'] ) { case 'html': case 'rich-text': - $block_reader = new WP_HTML_Tag_Processor( $block_content ); - - // TODO: Support for CSS selectors whenever they are ready in the HTML API. - // In the meantime, support comma-separated selectors by exploding them into an array. - $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] ); - // Add a bookmark to the first tag to be able to iterate over the selectors. - $block_reader->next_tag(); - $block_reader->set_bookmark( 'iterate-selectors' ); - - // TODO: This shouldn't be needed when the `set_inner_html` function is ready. - // Store the parent tag and its attributes to be able to restore them later in the button. - // The button block has a wrapper while the paragraph and heading blocks don't. - if ( 'core/button' === $block_name ) { - $button_wrapper = $block_reader->get_tag(); - $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); - $button_wrapper_attrs = array(); - foreach ( $button_wrapper_attribute_names as $name ) { - $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); - } + // 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'; } - - foreach ( $selectors as $selector ) { - // If the parent tag, or any of its children, matches the selector, replace the HTML. - if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( - array( - 'tag_name' => $selector, - ) - ) ) { - $block_reader->release_bookmark( 'iterate-selectors' ); - - // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. - // Until then, it is hardcoded for the paragraph, heading, and button blocks. - // Store the tag and its attributes to be able to restore them later. - $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); - $selector_attrs = array(); - foreach ( $selector_attribute_names as $name ) { - $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); - } - $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . ""; - $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); - $amended_content->next_tag(); - foreach ( $selector_attrs as $attribute_key => $attribute_value ) { - $amended_content->set_attribute( $attribute_key, $attribute_value ); - } - if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) { - return $amended_content->get_updated_html(); - } - if ( 'core/button' === $block_name ) { - $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}"; - $amended_button = new WP_HTML_Tag_Processor( $button_markup ); - $amended_button->next_tag(); - foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { - $amended_button->set_attribute( $attribute_key, $attribute_value ); - } - return $amended_button->get_updated_html(); - } + 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